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

Commit 2420f8b2 authored by Yeabkal Wubshit's avatar Yeabkal Wubshit Committed by Android (Google) Code Review
Browse files

Merge "API updates to ScrollFeedbackProvider" into main

parents 85311755 f134b194
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