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

Commit f134b194 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit
Browse files

API updates to ScrollFeedbackProvider

- Removed the APIs that took MotionEvent; now using only the ones that
  take the raw input device ID, source, and axis.
- Added details on when each API should be called.
- Added details on what the arguments values for the APIs are.
- Added IntDef for HapticScrollFeedbackProvider axis

Bug: 299647683
Test: atest HapticScrollFeedbackProviderTest
Change-Id: I5e1759fa4e4187d3b9e43ba028a565cf529cc7e6
parent 381a16ce
Loading
Loading
Loading
Loading
+0 −3
Original line number Diff line number Diff line
@@ -51231,11 +51231,8 @@ package android.view {
  @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API) public interface ScrollFeedbackProvider {
    method public void onScrollLimit(int, int, int, boolean);
    method public default void onScrollLimit(@NonNull android.view.MotionEvent, int, boolean);
    method public void onScrollProgress(int, int, int, int);
    method public default void onScrollProgress(@NonNull android.view.MotionEvent, int, int);
    method public void onSnapToItem(int, int, int);
    method public default void onSnapToItem(@NonNull android.view.MotionEvent, int);
  }
  public class SearchEvent {
+19 −7
Original line number Diff line number Diff line
@@ -17,21 +17,35 @@
package android.view;

import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.view.flags.Flags;

import com.android.internal.annotations.VisibleForTesting;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
 *
 * <p>Each scrolling widget should have its own instance of this class to ensure that scroll state
 * is isolated.
 *
 * <p>Check {@link ScrollFeedbackProvider} for details on the arguments that should be passed to the
 * methods in this class. To check if your input device ID, source, and motion axis are valid for
 * haptic feedback, you can use the
 * {@link ViewConfiguration#isHapticScrollFeedbackEnabled(int, int, int)} API.
 */
@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    private static final String TAG = "HapticScrollFeedbackProvider";

    /** @hide */
    @IntDef(value = {MotionEvent.AXIS_SCROLL})
    @Retention(RetentionPolicy.SOURCE)
    public @interface HapticScrollFeedbackAxis {}

    private static final int TICK_INTERVAL_NO_TICK = 0;

    private final View mView;
@@ -46,10 +60,6 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    /** The {@link InputDevice} source from which the latest scroll event happened. */
    private int mSource = -1;

    /**
     * Cache for tick interval for scroll tick caused by a {@link InputDevice#SOURCE_ROTARY_ENCODER}
     * on {@link MotionEvent#AXIS_SCROLL}. Set to -1 if the value has not been fetched and cached.
     */
    /** The tick interval corresponding to the current InputDevice/source/axis. */
    private int mTickIntervalPixels = TICK_INTERVAL_NO_TICK;
    private int mTotalScrollPixels = 0;
@@ -68,7 +78,8 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    }

    @Override
    public void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels) {
    public void onScrollProgress(
            int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, int deltaInPixels) {
        maybeUpdateCurrentConfig(inputDeviceId, source, axis);
        if (!mHapticScrollFeedbackEnabled) {
            return;
@@ -95,7 +106,8 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    }

    @Override
    public void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart) {
    public void onScrollLimit(
            int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis, boolean isStart) {
        maybeUpdateCurrentConfig(inputDeviceId, source, axis);
        if (!mHapticScrollFeedbackEnabled) {
            return;
@@ -112,7 +124,7 @@ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
    }

    @Override
    public void onSnapToItem(int inputDeviceId, int source, int axis) {
    public void onSnapToItem(int inputDeviceId, int source, @HapticScrollFeedbackAxis int axis) {
        maybeUpdateCurrentConfig(inputDeviceId, source, axis);
        if (!mHapticScrollFeedbackEnabled) {
            return;
+65 −82
Original line number Diff line number Diff line
@@ -24,120 +24,103 @@ import android.view.flags.Flags;
 * Interface to represent an entity giving consistent feedback for different events surrounding view
 * scroll.
 *
 * <p>When you have access to the {@link MotionEvent}s that triggered the different scroll events,
 * use the {@link MotionEvent} based APIs in this class. If you do not have access to the motion
 * events, you can use the methods that accept the {@link InputDevice} ID (which can be obtained by
 * APIs like {@link MotionEvent#getDeviceId()} and {@link InputDevice#getId()}) and source (which
 * can be obtained by APIs like {@link MotionEvent#getSource()}) of the motion that caused the
 * scroll events.
 * <p>The interface provides methods for the client to report different scroll events. The client
 * should report all scroll events that they want to be considered for scroll feedback using the
 * respective methods. The interface will process these events and provide scroll feedback based on
 * its specific feedback implementation.
 *
 * <h3>Obtaining the correct arguments for methods in this interface</h3>
 *
 * <p>Methods in this interface rely on the provision of valid {@link InputDevice} ID and source, as
 * well as the {@link MotionEvent} axis that generated a specific scroll event. The
 * {@link InputDevice} represented by the provided ID must have a {@link InputDevice.MotionRange}
 * with the provided source and axis. See below for more details on obtaining the right arguments
 * for your method call.
 *
 * <ul>
 *
 * <li><p><b>inputDeviceId</b>: should always be the ID of the {@link InputDevice} that generated
 * the scroll event. If calling this method in response to a {@link MotionEvent}, use the device ID
 * that is reported by the event, which can be obtained using {@link MotionEvent#getDeviceId()}.
 * Otherwise, use a valid ID that is obtained from {@link InputDevice#getId()}, or from an
 * {@link InputManager} instance ({@link InputManager#getInputDeviceIds()} gives all the valid input
 * device IDs).
 *
 * <li><p><b>source</b>: should always be the {@link InputDevice} source that generated the scroll
 * event. Use {@link MotionEvent#getSource()} if calling this method in response to a
 * {@link MotionEvent}. Otherwise, use a valid source for the {@link InputDevice}. You can use
 * {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s for the
 * {@link InputDevice}, from which you can derive all the valid sources for the device.
 *
 * <li><p><b>axis</b>: should always be the axis whose axis value produced the scroll event.
 * A {@link MotionEvent} may report data for multiple axes, and each axis may have multiple data
 * points for different pointers. Use the axis whose movement produced the specific scroll event.
 * The motion value for an axis can be obtained using {@link MotionEvent#getAxisValue(int)}.
 * You can use {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s
 * for the {@link InputDevice}, from which you can derive all the valid axes for the device.
 *
 * </ul>
 *
 * <b>Note</b> that not all valid input device source and motion axis inputs are necessarily
 * supported for scroll feedback. If you are implementing this interface, provide clear
 * documentation in your implementation class about which input device source and motion axis are
 * supported for your specific implementation. If you are using one of the implementations of this
 * interface, please refer to the documentation of the implementation for details on which input
 * device source and axis are supported.
 */
@FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
public interface ScrollFeedbackProvider {
    /**
     * Call this when the view has snapped to an item, with a motion generated by an
     * {@link InputDevice} with an id of {@code inputDeviceId}, from an input {@code source} and on
     * a given motion event {@code axis}.
     * Call this when the view has snapped to an item.
     *
     * <p>This method has the same purpose as {@link #onSnapToItem(MotionEvent, int)}. When a scroll
     * snap happens, call either this method or {@link #onSnapToItem(MotionEvent, int)}, not both.
     * This method is useful when you have no direct access to the {@link MotionEvent} that
     * caused the snap event.
     *
     * @param inputDeviceId the ID of the {@link InputDevice} that generated the motion triggering
     *          the snap.
     * @param source the input source of the motion causing the snap.
     * @param axis the axis of {@code event} that caused the item to snap.
     *
     * @see #onSnapToItem(MotionEvent, int)
     */
    void onSnapToItem(int inputDeviceId, int source, int axis);

    /**
     * Call this when the view has snapped to an item, with a motion from a given
     * {@link MotionEvent} on a given {@code axis}.
     *
     * <p>The interface is not aware of the internal scroll states of the view for which scroll
     * feedback is played. As such, the client should call
     * {@link #onScrollLimit(MotionEvent, int, int)} when scrolling has reached limit.
     *
     * @param event the {@link MotionEvent} that caused the item to snap.
     * @param axis the axis of {@code event} that caused the item to snap.
     *
     * @see #onSnapToItem(int, int, int)
     */
    default void onSnapToItem(@NonNull MotionEvent event, int axis) {
        onSnapToItem(event.getDeviceId(), event.getSource(), axis);
    }

    /**
     * Call this when the view has reached the scroll limit when scrolled by a motion generated by
     * an {@link InputDevice} with an id of {@code inputDeviceId}, from an input {@code source} and
     * on a given motion event {@code axis}.
     * Call this when the view has reached the scroll limit.
     *
     * <p>This method has the same purpose as {@link #onScrollLimit(MotionEvent, int, boolean)}.
     * When a scroll limit happens, call either this method or
     * {@link #onScrollLimit(MotionEvent, int, boolean)}, not both. This method is useful when you
     * have no direct access to the {@link MotionEvent} that caused the scroll limit.
     * <p>Note that a feedback may not be provided on every call to this method. This interface, for
     * instance, may provide feedback on every `N`th scroll limit event. For the interface to
     * properly provide feedback when needed, call this method for each scroll limit event that you
     * want to be accounted to scroll limit feedback.
     *
     * @param inputDeviceId the ID of the {@link InputDevice} that caused scrolling to hit limit.
     * @param source the input source of the motion that caused scrolling to hit the limit.
     * @param axis the axis of {@code event} that caused scrolling to hit the limit.
     * @param isStart {@code true} if scrolling hit limit at the start of the scrolling list, and
     *                {@code false} if the scrolling hit limit at the end of the scrolling list.
     *
     * @see #onScrollLimit(MotionEvent, int, boolean)
     *                <i>start</i> and <i>end<i> in this context are not geometrical references.
     *                Instead, they refer to the start and end of a scrolling experience. As such,
     *                "start" for some views may be at the bottom of a scrolling list, while it may
     *                be at the top of scrolling list for others.
     */
    void onScrollLimit(int inputDeviceId, int source, int axis, boolean isStart);

    /**
     * Call this when the view has reached the scroll limit when scrolled by the motion from a given
     * {@link MotionEvent} on a given {@code axis}.
     *
     * @param event the {@link MotionEvent} that caused scrolling to hit the limit.
     * @param axis the axis of {@code event} that caused scrolling to hit the limit.
     * @param isStart {@code true} if scrolling hit limit at the start of the scrolling list, and
     *                {@code false} if the scrolling hit limit at the end of the scrolling list.
     * Call this when the view has scrolled.
     *
     * @see #onScrollLimit(int, int, int, boolean)
     */
    default void onScrollLimit(@NonNull MotionEvent event, int axis, boolean isStart) {
        onScrollLimit(event.getDeviceId(), event.getSource(), axis, isStart);
    }

    /**
     * Call this when the view has scrolled by {@code deltaInPixels} due to the motion generated by
     * an {@link InputDevice} with an id of {@code inputDeviceId}, from an input {@code source} and
     * on a given motion event {@code axis}.
     * <p>Different axes have different ways to map their raw axis values to pixels for scrolling.
     * When calling this method, use the scroll values in pixels by which the view was scrolled; do
     * not use the raw axis values. That is, use whatever value is passed to one of View's scrolling
     * methods (example: {@link View#scrollBy(int, int)}). For example, for vertical scrolling on
     * {@link MotionEvent#AXIS_SCROLL}, convert the raw axis value to the equivalent pixels by using
     * {@link ViewConfiguration#getScaledVerticalScrollFactor()}, and use that value for this method
     * call.
     *
     * <p>This method has the same purpose as {@link #onScrollProgress(MotionEvent, int, int)}.
     * When a scroll progress happens, call either this method or
     * {@link #onScrollProgress(MotionEvent, int, int)}, not both. This method is useful when you
     * have no direct access to the {@link MotionEvent} that caused the scroll progress.
     * <p>Note that a feedback may not be provided on every call to this method. This interface, for
     * instance, may provide feedback for every `x` pixels scrolled. For the interface to properly
     * track scroll progress and provide feedback when needed, call this method for each scroll
     * event that you want to be accounted to scroll feedback.
     *
     * @param inputDeviceId the ID of the {@link InputDevice} that caused scroll progress.
     * @param source the input source of the motion that caused scroll progress.
     * @param axis the axis of {@code event} that caused scroll progress.
     * @param deltaInPixels the amount of scroll progress, in pixels.
     *
     * @see #onScrollProgress(MotionEvent, int, int)
     */
    void onScrollProgress(int inputDeviceId, int source, int axis, int deltaInPixels);

    /**
     * Call this when the view has scrolled by {@code deltaInPixels} due to the motion from a given
     * {@link MotionEvent} on a given {@code axis}.
     *
     * <p>The interface is not aware of the internal scroll states of the view for which scroll
     * feedback is played. As such, the client should call
     * {@link #onScrollLimit(MotionEvent, int, int)} when scrolling has reached limit.
     *
     * @param event the {@link MotionEvent} that caused scroll progress.
     * @param axis the axis of {@code event} that caused scroll progress.
     * @param deltaInPixels the amount of scroll progress, in pixels.
     *
     * @see #onScrollProgress(int, int, int, int)
     */
    default void onScrollProgress(@NonNull MotionEvent event, int axis, int deltaInPixels) {
        onScrollProgress(event.getDeviceId(), event.getSource(), axis, deltaInPixels);
    }
}
+28 −20
Original line number Diff line number Diff line
@@ -1220,36 +1220,39 @@ public class ViewConfiguration {
     * Checks if any kind of scroll haptic feedback is enabled for a motion generated by a specific
     * input device configuration and motion axis.
     *
     * <h3>Obtaining the correct arguments for this method call</h3>
     * <p><b>inputDeviceId</b>: if calling this method in response to a {@link MotionEvent}, use
     * the device ID that is reported by the event, which can be obtained using
     * {@link MotionEvent#getDeviceId()}. Otherwise, use a valid ID that is obtained from
     * {@link InputDevice#getId()}, or from an {@link InputManager} instance
     * ({@link InputManager#getInputDeviceIds()} gives all the valid input device IDs).
     * <p>See {@link ScrollFeedbackProvider} for details on the arguments that should be passed to
     * the methods in this class.
     *
     * <p><b>axis</b>: a {@link MotionEvent} may report data for multiple axes, and each axis may
     * have multiple data points for different pointers. Use the axis whose movement produced the
     * scrolls that would generate the scroll haptics. You can use
     * {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s for the
     * {@link InputDevice}, from which you can derive all the valid axes for the device.
     * <p>If the provided input device ID, source, and motion axis are not supported by this Android
     * device, this method returns {@code false}. In other words, if the {@link InputDevice}
     * represented by the provided {code inputDeviceId} does not have a
     * {@link InputDevice.MotionRange} with the provided {@code axis} and {@code source}, the method
     * returns {@code false}.
     *
     * <p><b>source</b>: use {@link MotionEvent#getSource()} if calling this method in response to a
     * {@link MotionEvent}. Otherwise, use a valid source for the {@link InputDevice}. You can use
     * {@link InputDevice#getMotionRanges()} to get all the {@link InputDevice.MotionRange}s for the
     * {@link InputDevice}, from which you can derive all the valid sources for the device.
     * <p>If the provided input device ID, source, and motion axis are supported by this Android
     * device, this method returns {@code true} only if the provided arguments are supported for
     * scroll haptics. Otherwise, this method returns {@code false}.
     *
     * @param inputDeviceId the ID of the {@link InputDevice} that generated the motion that may
     *      produce scroll haptics.
     * @param source the input source of the motion that may produce scroll haptics.
     * @param axis the axis of the motion that may produce scroll haptics.
     * @return {@code true} if motions generated by the provided input and motion configuration
     *      should produce scroll haptics. {@code false} otherwise.
     *      can produce scroll haptics. {@code false} otherwise.
     *
     * @see #getHapticScrollFeedbackTickInterval(int, int, int)
     * @see InputDevice#getMotionRanges()
     * @see InputDevice#getMotionRange(int)
     * @see InputDevice#getMotionRange(int, int)
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    public boolean isHapticScrollFeedbackEnabled(int inputDeviceId, int axis, int source) {
    public boolean isHapticScrollFeedbackEnabled(
            int inputDeviceId,
            @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
            int source) {
        if (!isInputDeviceInfoValid(inputDeviceId, axis, source)) return false;

        if (source == InputDevice.SOURCE_ROTARY_ENCODER) {
        if (source == InputDevice.SOURCE_ROTARY_ENCODER && axis == MotionEvent.AXIS_SCROLL) {
            return mRotaryEncoderHapticScrollFeedbackEnabled;
        }

@@ -1285,9 +1288,14 @@ public class ViewConfiguration {
     *      configuration. If scroll haptics is disabled for the given configuration, or if the
     *      device does not support scroll tick haptics for the given configuration, this method
     *      returns {@code Integer.MAX_VALUE}.
     *
     * @see #isHapticScrollFeedbackEnabled(int, int, int)
     */
    @FlaggedApi(Flags.FLAG_SCROLL_FEEDBACK_API)
    public int getHapticScrollFeedbackTickInterval(int inputDeviceId, int axis, int source) {
    public int getHapticScrollFeedbackTickInterval(
            int inputDeviceId,
            @HapticScrollFeedbackProvider.HapticScrollFeedbackAxis int axis,
            int source) {
        if (!mRotaryEncoderHapticScrollFeedbackEnabled) {
            return NO_HAPTIC_SCROLL_TICK_INTERVAL;
        }
@@ -1296,7 +1304,7 @@ public class ViewConfiguration {
            return NO_HAPTIC_SCROLL_TICK_INTERVAL;
        }

        if (source == InputDevice.SOURCE_ROTARY_ENCODER) {
        if (source == InputDevice.SOURCE_ROTARY_ENCODER && axis == MotionEvent.AXIS_SCROLL) {
            return mRotaryEncoderHapticScrollFeedbackTickIntervalPixels;
        }

+4 −2
Original line number Diff line number Diff line
@@ -4520,7 +4520,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                    if (!trackMotionScroll(delta, delta)) {
                        if (Flags.platformWidgetHapticScrollFeedback()) {
                            initHapticScrollFeedbackProviderIfNotExists();
                            mHapticScrollFeedbackProvider.onScrollProgress(event, axis, delta);
                            mHapticScrollFeedbackProvider.onScrollProgress(
                                    event.getDeviceId(), event.getSource(), axis, delta);
                        }
                        initDifferentialFlingHelperIfNotExists();
                        mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
@@ -4536,7 +4537,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                        if (Flags.platformWidgetHapticScrollFeedback()) {
                            initHapticScrollFeedbackProviderIfNotExists();
                            mHapticScrollFeedbackProvider.onScrollLimit(
                                    event, axis, /* isStart= */ hitTopLimit);
                                    event.getDeviceId(), event.getSource(), axis,
                                    /* isStart= */ hitTopLimit);
                        }
                        if (hitTopLimit) {
                            mEdgeGlowTop.onPullDistance(overscroll, 0.5f);
Loading