Loading core/api/current.txt +0 −3 Original line number Diff line number Diff line Loading @@ -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 { core/java/android/view/HapticScrollFeedbackProvider.java +19 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading core/java/android/view/ScrollFeedbackProvider.java +65 −82 Original line number Diff line number Diff line Loading @@ -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); } } core/java/android/view/ViewConfiguration.java +28 −20 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; } Loading core/java/android/widget/AbsListView.java +4 −2 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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 Loading
core/api/current.txt +0 −3 Original line number Diff line number Diff line Loading @@ -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 {
core/java/android/view/HapticScrollFeedbackProvider.java +19 −7 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading
core/java/android/view/ScrollFeedbackProvider.java +65 −82 Original line number Diff line number Diff line Loading @@ -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); } }
core/java/android/view/ViewConfiguration.java +28 −20 Original line number Diff line number Diff line Loading @@ -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; } Loading Loading @@ -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; } Loading @@ -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; } Loading
core/java/android/widget/AbsListView.java +4 −2 Original line number Diff line number Diff line Loading @@ -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); Loading @@ -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