Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0a884b6d authored by Mina Granic's avatar Mina Granic Committed by Android (Google) Code Review
Browse files

Merge changes Ifc0b5381,I01f57aac into main

* changes:
  Cleanup LetterboxScrollProcessor with @NonNull and similar.
  Check event in app's bounds using absolute coordinates.
parents ccea1b24 9b38acc8
Loading
Loading
Loading
Loading
+29 −11
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package android.view;
package android.view;


import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;

import android.annotation.Nullable;
import android.annotation.Nullable;
import android.content.Context;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Rect;
@@ -23,6 +25,8 @@ import android.os.Handler;


import androidx.annotation.NonNull;
import androidx.annotation.NonNull;


import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HashSet;
import java.util.List;
import java.util.List;
@@ -35,6 +39,7 @@ import java.util.Set;
 *
 *
 * @hide
 * @hide
 */
 */
@VisibleForTesting(visibility = PACKAGE)
public class LetterboxScrollProcessor {
public class LetterboxScrollProcessor {


    private enum LetterboxScrollState {
    private enum LetterboxScrollState {
@@ -53,6 +58,7 @@ public class LetterboxScrollProcessor {
    /** IDs of events generated from this class */
    /** IDs of events generated from this class */
    private final Set<Integer> mGeneratedEventIds = new HashSet<>();
    private final Set<Integer> mGeneratedEventIds = new HashSet<>();


    @VisibleForTesting(visibility = PACKAGE)
    public LetterboxScrollProcessor(@NonNull Context context, @Nullable Handler handler) {
    public LetterboxScrollProcessor(@NonNull Context context, @Nullable Handler handler) {
        mContext = context;
        mContext = context;
        mScrollDetector = new GestureDetector(context, new ScrollListener(), handler);
        mScrollDetector = new GestureDetector(context, new ScrollListener(), handler);
@@ -69,7 +75,9 @@ public class LetterboxScrollProcessor {
     * @return The list of adjusted events, or null if no adjustments are needed. The list is empty
     * @return The list of adjusted events, or null if no adjustments are needed. The list is empty
     * if the event should be ignored. Do not keep a reference to the output as the list is reused.
     * if the event should be ignored. Do not keep a reference to the output as the list is reused.
     */
     */
    public List<MotionEvent> processMotionEvent(MotionEvent motionEvent) {
    @Nullable
    @VisibleForTesting(visibility = PACKAGE)
    public List<MotionEvent> processMotionEvent(@NonNull MotionEvent motionEvent) {
        mProcessedEvents.clear();
        mProcessedEvents.clear();
        final Rect appBounds = getAppBounds();
        final Rect appBounds = getAppBounds();


@@ -124,11 +132,9 @@ public class LetterboxScrollProcessor {
            mState = LetterboxScrollState.AWAITING_GESTURE_START;
            mState = LetterboxScrollState.AWAITING_GESTURE_START;
        }
        }


        if (makeNoAdjustments) return null;
        return makeNoAdjustments ? null : mProcessedEvents;
        return mProcessedEvents;
    }
    }



    /**
    /**
     * Processes the InputEvent for compatibility before it is finished by calling
     * Processes the InputEvent for compatibility before it is finished by calling
     * InputEventReceiver#finishInputEvent().
     * InputEventReceiver#finishInputEvent().
@@ -136,21 +142,33 @@ public class LetterboxScrollProcessor {
     * @param motionEvent The MotionEvent to process.
     * @param motionEvent The MotionEvent to process.
     * @return The motionEvent to finish, or null if it should not be finished.
     * @return The motionEvent to finish, or null if it should not be finished.
     */
     */
    public InputEvent processMotionEventBeforeFinish(MotionEvent motionEvent) {
    @Nullable
        if (mGeneratedEventIds.remove(motionEvent.getId())) return null;
    @VisibleForTesting(visibility = PACKAGE)
        return motionEvent;
    public InputEvent processMotionEventBeforeFinish(@NonNull MotionEvent motionEvent) {
        return mGeneratedEventIds.remove(motionEvent.getId()) ? null : motionEvent;
    }
    }


    @NonNull
    private Rect getAppBounds() {
    private Rect getAppBounds() {
        return mContext.getResources().getConfiguration().windowConfiguration.getBounds();
        return mContext.getResources().getConfiguration().windowConfiguration.getBounds();
    }
    }


    private boolean isOutsideAppBounds(MotionEvent motionEvent, Rect appBounds) {
    /** Checks whether the gesture is located on the letterbox area. */
        return motionEvent.getX() < 0 || motionEvent.getX() >= appBounds.width()
    private boolean isOutsideAppBounds(@NonNull MotionEvent motionEvent, @NonNull Rect appBounds) {
                || motionEvent.getY() < 0 || motionEvent.getY() >= appBounds.height();
        // The events are in the coordinate system of the ViewRootImpl (window). The window might
        // not have the same dimensions as the app bounds - for example in case of Dialogs - thus
        // `getRawX()` and `getRawY()` are used, with the absolute bounds (left, top, etc) instead
        // of width and height.
        // The event should be passed to the app if it has happened anywhere in the app area,
        // irrespective of the current window size, therefore the app bounds are used instead of the
        // current window.
        return motionEvent.getRawX() < appBounds.left
                || motionEvent.getRawX() >= appBounds.right
                || motionEvent.getRawY() < appBounds.top
                || motionEvent.getRawY() >= appBounds.bottom;
    }
    }


    private void applyOffset(MotionEvent event, Rect appBounds) {
    private void applyOffset(@NonNull MotionEvent event, @NonNull Rect appBounds) {
        float horizontalOffset = calculateOffset(event.getX(), appBounds.width());
        float horizontalOffset = calculateOffset(event.getX(), appBounds.width());
        float verticalOffset = calculateOffset(event.getY(), appBounds.height());
        float verticalOffset = calculateOffset(event.getY(), appBounds.height());
        // Apply the offset to the motion event so it is over the app's view.
        // Apply the offset to the motion event so it is over the app's view.
+105 −42
Original line number Original line Diff line number Diff line
@@ -31,13 +31,13 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Looper;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.Presubmit;


import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.platform.app.InstrumentationRegistry;


import org.junit.Before;
import org.junit.Before;
import org.junit.Test;
import org.junit.Test;



import java.util.ArrayList;
import java.util.ArrayList;
import java.util.List;
import java.util.List;


@@ -54,30 +54,32 @@ public class LetterboxScrollProcessorTest {
    private LetterboxScrollProcessor mLetterboxScrollProcessor;
    private LetterboxScrollProcessor mLetterboxScrollProcessor;
    private Context mContext;
    private Context mContext;


    // Constant delta used when comparing coordinates (floats)
    // Constant delta used when comparing coordinates (floats).
    private static final float EPSILON = 0.1f;
    private static final float EPSILON = 0.1f;


    private static final Rect APP_BOUNDS =
            new Rect(/* left= */ 200, /* top= */ 200, /* right= */ 600, /* bottom= */ 1000);

    @Before
    @Before
    public void setUp() {
    public void setUp() {
        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();


        // Set app bounds as if it was letterboxed.
        // Set app bounds as if it was letterboxed.
        mContext.getResources().getConfiguration().windowConfiguration
        mContext.getResources().getConfiguration().windowConfiguration.setBounds(APP_BOUNDS);
                .setBounds(new Rect(200, 200, 600, 1000));

        Handler handler = new Handler(Looper.getMainLooper());


        // Recreate to reset LetterboxScrollProcessor state.
        // Recreate to reset LetterboxScrollProcessor state.
        mLetterboxScrollProcessor = new LetterboxScrollProcessor(mContext, handler);
        mLetterboxScrollProcessor = new LetterboxScrollProcessor(mContext,
                new Handler(Looper.getMainLooper()));
    }
    }


    @Test
    @Test
    public void testGestureInBoundsHasNoAdjustments() {
    public void testGestureInBoundsHasNoAdjustments() {
        // Tap-like gesture in bounds (non-scroll).
        // Tap-like gesture in bounds (non-scroll).
        List<MotionEvent> tapGestureEvents = createTapGestureEvents(0f, 0f);
        final List<MotionEvent> tapGestureEvents = createTapGestureEvents(
                /* startX= */ 0f, /* startY= */ 0f);


        // Get processed events from Letterbox Scroll Processor.
        // Get processed events from Letterbox Scroll Processor.
        List<MotionEvent> processedEvents = processMotionEvents(tapGestureEvents);
        final List<MotionEvent> processedEvents = processMotionEvents(tapGestureEvents);


        // Ensure no changes are made to events after processing - event locations should not be
        // Ensure no changes are made to events after processing - event locations should not be
        // adjusted because the gesture started in the app's bounds (for all gestures).
        // adjusted because the gesture started in the app's bounds (for all gestures).
@@ -86,13 +88,32 @@ public class LetterboxScrollProcessorTest {
        assertMotionEventsShouldBeFinished(processedEvents);
        assertMotionEventsShouldBeFinished(processedEvents);
    }
    }


    @Test
    public void testGestureInAppBoundsButOutsideTopWindowAlsoForwardedToTheApp() {
        final Rect dialogBounds =
                new Rect(/* left= */ 300, /* top= */ 500, /* right= */ 500, /* bottom= */ 700);
        // Tap-like gesture outside the dialog, but in app bounds.
        List<MotionEvent> tapGestureEvents = createTapGestureEventsWithCoordinateSystem(0f, 0f,
                dialogBounds);

        // Get processed events from Letterbox Scroll Processor.
        List<MotionEvent> processedEvents = processMotionEvents(tapGestureEvents);

        // Ensure no changes are made to events after processing - the event should be forwarded as
        // normal.
        assertEventLocationsAreNotAdjusted(tapGestureEvents, processedEvents);
        // Ensure all of these events should be finished (expect no generated events).
        assertMotionEventsShouldBeFinished(processedEvents);
    }

    @Test
    @Test
    public void testGestureOutsideBoundsIsIgnored() {
    public void testGestureOutsideBoundsIsIgnored() {
        // Tap-like gesture outside bounds (non-scroll).
        // Tap-like gesture outside bounds (non-scroll).
        List<MotionEvent> tapGestureEvents = createTapGestureEvents(-100f, -100f);
        final List<MotionEvent> tapGestureEvents = createTapGestureEvents(
                /* startX= */ -100f, /* startY= */ -100f);


        // Get processed events from Letterbox Scroll Processor.
        // Get processed events from Letterbox Scroll Processor.
        List<MotionEvent> processedEvents = processMotionEvents(tapGestureEvents);
        final List<MotionEvent> processedEvents = processMotionEvents(tapGestureEvents);


        // All events should be ignored since it was a non-scroll gesture and out of bounds.
        // All events should be ignored since it was a non-scroll gesture and out of bounds.
        assertEquals(0, processedEvents.size());
        assertEquals(0, processedEvents.size());
@@ -101,10 +122,11 @@ public class LetterboxScrollProcessorTest {
    @Test
    @Test
    public void testScrollGestureInBoundsHasNoAdjustments() {
    public void testScrollGestureInBoundsHasNoAdjustments() {
        // Scroll gesture in bounds (non-scroll).
        // Scroll gesture in bounds (non-scroll).
        List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(0f, 0f);
        final List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(
                /* startX= */ 0f, /* startY= */ 0f);


        // Get processed events from Letterbox Scroll Processor.
        // Get processed events from Letterbox Scroll Processor.
        List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);
        final List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);


        // Ensure no changes are made to events after processing - event locations should not be
        // Ensure no changes are made to events after processing - event locations should not be
        // adjusted because the gesture started in the app's bounds (for all gestures).
        // adjusted because the gesture started in the app's bounds (for all gestures).
@@ -116,10 +138,11 @@ public class LetterboxScrollProcessorTest {
    @Test
    @Test
    public void testScrollGestureInBoundsThenLeavesBoundsHasNoAdjustments() {
    public void testScrollGestureInBoundsThenLeavesBoundsHasNoAdjustments() {
        // Scroll gesture in bounds (non-scroll) that moves out of bounds.
        // Scroll gesture in bounds (non-scroll) that moves out of bounds.
        List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(390f, 790f);
        final List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(
                /* startX= */ 390f, /* startY= */ 790f);


        // Get processed events from Letterbox Scroll Processor.
        // Get processed events from Letterbox Scroll Processor.
        List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);
        final List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);


        // Ensure no changes are made to events after processing - event locations should not be
        // Ensure no changes are made to events after processing - event locations should not be
        // adjusted because the gesture started in the app's bounds (for all gestures), even if it
        // adjusted because the gesture started in the app's bounds (for all gestures), even if it
@@ -132,7 +155,8 @@ public class LetterboxScrollProcessorTest {
    @Test
    @Test
    public void testScrollGestureOutsideBoundsIsStartedInBounds() {
    public void testScrollGestureOutsideBoundsIsStartedInBounds() {
        // Scroll gesture outside bounds.
        // Scroll gesture outside bounds.
        List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(-100f, 0f);
        List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(
                /* startX= */ -100f, /* startY= */ 0f);


        // Get processed events from Letterbox Scroll Processor.
        // Get processed events from Letterbox Scroll Processor.
        List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);
        List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);
@@ -142,9 +166,9 @@ public class LetterboxScrollProcessorTest {


        // Ensure offset ACTION_DOWN is first event received.
        // Ensure offset ACTION_DOWN is first event received.
        MotionEvent firstProcessedEvent = processedEvents.getFirst();
        MotionEvent firstProcessedEvent = processedEvents.getFirst();
        assertEquals(firstProcessedEvent.getAction(), ACTION_DOWN);
        assertEquals(ACTION_DOWN, firstProcessedEvent.getAction());
        assertEquals(firstProcessedEvent.getX(), 0, EPSILON);
        assertEquals(0, firstProcessedEvent.getX(), EPSILON);
        assertEquals(firstProcessedEvent.getY(), 0, EPSILON);
        assertEquals(0, firstProcessedEvent.getY(), EPSILON);
        // Ensure this event is not finished (because it was generated by LetterboxScrollProcessor).
        // Ensure this event is not finished (because it was generated by LetterboxScrollProcessor).
        assertNull(mLetterboxScrollProcessor.processMotionEventBeforeFinish(firstProcessedEvent));
        assertNull(mLetterboxScrollProcessor.processMotionEventBeforeFinish(firstProcessedEvent));
    }
    }
@@ -152,16 +176,17 @@ public class LetterboxScrollProcessorTest {
    @Test
    @Test
    public void testScrollGestureOutsideBoundsIsMovedInBounds() {
    public void testScrollGestureOutsideBoundsIsMovedInBounds() {
        // Scroll gesture outside bounds.
        // Scroll gesture outside bounds.
        List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(-100f, 0f);
        final List<MotionEvent> scrollGestureEvents = createScrollGestureEvents(
                /* startX= */ -100f, /* startY= */ 0f);


        // Get processed events from Letterbox Scroll Processor.
        // Get processed events from Letterbox Scroll Processor.
        List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);
        final List<MotionEvent> processedEvents = processMotionEvents(scrollGestureEvents);


        // When a scroll occurs outside bounds: once detected as a scroll, an offset ACTION_DOWN is
        // When a scroll occurs outside bounds: once detected as a scroll, an offset ACTION_DOWN is
        // placed and then the rest of the gesture is offset also. Some ACTION_MOVE events may be
        // placed and then the rest of the gesture is offset also. Some ACTION_MOVE events may be
        // ignored until the gesture is 'detected as a scroll'.
        // ignored until the gesture is 'detected as a scroll'.
        // For this test, we expect the first ACTION_MOVE event to be ignored:
        // For this test, we expect the first ACTION_MOVE event to be ignored:
        scrollGestureEvents.remove(1);
        scrollGestureEvents.remove(/* index= */ 1);


        // Ensure all processed events (that are not ignored) are offset over the app.
        // Ensure all processed events (that are not ignored) are offset over the app.
        assertXCoordinatesAdjustedToZero(scrollGestureEvents, processedEvents);
        assertXCoordinatesAdjustedToZero(scrollGestureEvents, processedEvents);
@@ -170,8 +195,9 @@ public class LetterboxScrollProcessorTest {
        assertMotionEventsShouldBeFinished(processedEvents.subList(1, processedEvents.size()));
        assertMotionEventsShouldBeFinished(processedEvents.subList(1, processedEvents.size()));
    }
    }


    private List<MotionEvent> processMotionEvents(List<MotionEvent> motionEvents) {
    @NonNull
        List<MotionEvent> processedEvents = new ArrayList<>();
    private List<MotionEvent> processMotionEvents(@NonNull List<MotionEvent> motionEvents) {
        final List<MotionEvent> processedEvents = new ArrayList<>();
        for (MotionEvent motionEvent : motionEvents) {
        for (MotionEvent motionEvent : motionEvents) {
            MotionEvent clonedEvent = MotionEvent.obtain(motionEvent);
            MotionEvent clonedEvent = MotionEvent.obtain(motionEvent);
            List<MotionEvent> letterboxScrollCompatEvents =
            List<MotionEvent> letterboxScrollCompatEvents =
@@ -187,39 +213,76 @@ public class LetterboxScrollProcessorTest {
        return processedEvents;
        return processedEvents;
    }
    }


    /**
     * Creates and returns a tap gesture with X and Y in reference to the app bounds (top left
     * corner is x=0, y=0).
     */
    @NonNull
    private List<MotionEvent> createTapGestureEvents(float startX, float startY) {
    private List<MotionEvent> createTapGestureEvents(float startX, float startY) {
        return createTapGestureEventsWithCoordinateSystem(startX, startY, APP_BOUNDS);
    }

    /**
     * @param referenceWindowBounds the amount the event will be translated by.
     */
    private List<MotionEvent> createTapGestureEventsWithCoordinateSystem(float startX, float startY,
            @NonNull Rect referenceWindowBounds) {
        // Events for tap-like gesture (non-scroll)
        // Events for tap-like gesture (non-scroll)
        List<MotionEvent> motionEvents = new ArrayList<>();
        List<MotionEvent> motionEvents = new ArrayList<>();
        motionEvents.add(createBasicMotionEvent(0, ACTION_DOWN, startX, startY));
        motionEvents.add(createBasicMotionEventWithCoordinateSystem(0, ACTION_DOWN,
        motionEvents.add(createBasicMotionEvent(10, ACTION_UP, startX , startY));
                startX, startY, referenceWindowBounds));
        motionEvents.add(createBasicMotionEventWithCoordinateSystem(10, ACTION_UP,
                startX , startY, referenceWindowBounds));
        return motionEvents;
        return motionEvents;
    }
    }


    @NonNull
    private List<MotionEvent> createScrollGestureEvents(float startX, float startY) {
    private List<MotionEvent> createScrollGestureEvents(float startX, float startY) {
        float touchSlop = (float) ViewConfiguration.get(mContext).getScaledTouchSlop();
        final float touchSlop = (float) ViewConfiguration.get(mContext).getScaledTouchSlop();


        // Events for scroll gesture (starts at (startX, startY) then moves down-right
        // Events for scroll gesture (starts at (startX, startY) then moves down-right.
        List<MotionEvent> motionEvents = new ArrayList<>();
        final List<MotionEvent> motionEvents = new ArrayList<>();
        motionEvents.add(createBasicMotionEvent(0, ACTION_DOWN, startX, startY));
        motionEvents.add(createBasicMotionEvent(/* downTime= */ 0, ACTION_DOWN, startX, startY));
        motionEvents.add(createBasicMotionEvent(10, ACTION_MOVE,
        motionEvents.add(createBasicMotionEvent(/* downTime= */ 10, ACTION_MOVE,
                startX + touchSlop / 2, startY + touchSlop / 2));
                startX + touchSlop / 2, startY + touchSlop / 2));
        // Below event is first event in the scroll gesture where distance > touchSlop
        // Below event is first event in the scroll gesture where distance > touchSlop.
        motionEvents.add(createBasicMotionEvent(20, ACTION_MOVE,
        motionEvents.add(createBasicMotionEvent(/* downTime= */ 20, ACTION_MOVE,
                startX + touchSlop * 2, startY + touchSlop * 2));
                startX + touchSlop * 2, startY + touchSlop * 2));
        motionEvents.add(createBasicMotionEvent(30, ACTION_MOVE,
        motionEvents.add(createBasicMotionEvent(/* downTime= */ 30, ACTION_MOVE,
                startX + touchSlop * 3, startY + touchSlop * 3));
                startX + touchSlop * 3, startY + touchSlop * 3));
        motionEvents.add(createBasicMotionEvent(40, ACTION_UP,
        motionEvents.add(createBasicMotionEvent(/* downTime= */ 40, ACTION_UP,
                startX + touchSlop * 3, startY + touchSlop * 3));
                startX + touchSlop * 3, startY + touchSlop * 3));
        return motionEvents;
        return motionEvents;
    }
    }


    private MotionEvent createBasicMotionEvent(int downTime, int action, float x, float y) {
    /**
        return MotionEvent.obtain(0, downTime, action, x, y, 0);
     * Creates and returns an event with X and Y in reference to the app bounds (top left corner is
     * x=0, y=0).
     */
    @NonNull
    private MotionEvent createBasicMotionEvent(int eventTime, int action, float x, float y) {
        return createBasicMotionEventWithCoordinateSystem(eventTime, action, x, y, APP_BOUNDS);
    }

    /**
     * @param referenceWindowBounds the amount the event will be translated by.
     */
    @NonNull
    private MotionEvent createBasicMotionEventWithCoordinateSystem(int eventTime, int action,
            float x, float y, @NonNull Rect referenceWindowBounds) {
        final float rawX = referenceWindowBounds.left + x;
        final float rawY = referenceWindowBounds.top + y;
        // RawX and RawY cannot be changed once the event is created. Therefore, pass rawX and rawY
        // according to the app's bounds on the display, and then offset to make X and Y relative to
        // the app's bounds.
        final MotionEvent event = MotionEvent.obtain(0, eventTime, action, rawX, rawY, 0);
        event.offsetLocation(-referenceWindowBounds.left, -referenceWindowBounds.top);
        return event;
    }
    }


    private void assertEventLocationsAreNotAdjusted(
    private void assertEventLocationsAreNotAdjusted(
            List<MotionEvent> originalEvents,
            @NonNull List<MotionEvent> originalEvents,
            List<MotionEvent> processedEvents) {
            @NonNull List<MotionEvent> processedEvents) {
        assertEquals("MotionEvent arrays are not the same size",
        assertEquals("MotionEvent arrays are not the same size",
                originalEvents.size(), processedEvents.size());
                originalEvents.size(), processedEvents.size());


@@ -232,8 +295,8 @@ public class LetterboxScrollProcessorTest {
    }
    }


    private void assertXCoordinatesAdjustedToZero(
    private void assertXCoordinatesAdjustedToZero(
            List<MotionEvent> originalEvents,
            @NonNull List<MotionEvent> originalEvents,
            List<MotionEvent> processedEvents) {
            @NonNull List<MotionEvent> processedEvents) {
        assertEquals("MotionEvent arrays are not the same size",
        assertEquals("MotionEvent arrays are not the same size",
                originalEvents.size(), processedEvents.size());
                originalEvents.size(), processedEvents.size());


@@ -245,7 +308,7 @@ public class LetterboxScrollProcessorTest {
        }
        }
    }
    }


    private void assertMotionEventsShouldBeFinished(List<MotionEvent> processedEvents) {
    private void assertMotionEventsShouldBeFinished(@NonNull List<MotionEvent> processedEvents) {
        for (MotionEvent processedEvent : processedEvents) {
        for (MotionEvent processedEvent : processedEvents) {
            assertNotNull(mLetterboxScrollProcessor.processMotionEventBeforeFinish(processedEvent));
            assertNotNull(mLetterboxScrollProcessor.processMotionEventBeforeFinish(processedEvent));
        }
        }