Loading core/java/android/view/HapticFeedbackConstants.java +6 −8 Original line number Diff line number Diff line Loading @@ -129,27 +129,25 @@ public class HapticFeedbackConstants { public static final int REJECT = 17; /** * A haptic effect to provide texture while a rotary input device is being scrolled. * A haptic effect to provide texture while scrolling. * * @hide */ public static final int ROTARY_SCROLL_TICK = 18; public static final int SCROLL_TICK = 18; /** * A haptic effect to signal that a list element has been focused while scrolling using a rotary * input device. * A haptic effect to signal that a list element has been focused while scrolling. * * @hide */ public static final int ROTARY_SCROLL_ITEM_FOCUS = 19; public static final int SCROLL_ITEM_FOCUS = 19; /** * A haptic effect to signal reaching the scrolling limits of a list while scrolling using a * rotary input device. * A haptic effect to signal reaching the scrolling limits of a list while scrolling. * * @hide */ public static final int ROTARY_SCROLL_LIMIT = 20; public static final int SCROLL_LIMIT = 20; /** * The user has toggled a switch or button into the on position. Loading core/java/android/view/HapticScrollFeedbackProvider.java 0 → 100644 +141 −0 Original line number Diff line number Diff line /** * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import static com.android.internal.R.dimen.config_rotaryEncoderAxisScrollTickInterval; import com.android.internal.annotations.VisibleForTesting; /** * {@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. * * @hide */ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { private static final String TAG = "HapticScrollFeedbackProvider"; private static final int TICK_INTERVAL_NO_TICK = 0; private static final int TICK_INTERVAL_UNSET = Integer.MAX_VALUE; private final View mView; // Info about the cause of the latest scroll event. /** The ID of the {link @InputDevice} that caused the latest scroll event. */ private int mDeviceId = -1; /** The axis on which the latest scroll event happened. */ private int mAxis = -1; /** 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. */ private int mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_UNSET; /** The tick interval corresponding to the current InputDevice/source/axis. */ private int mTickIntervalPixels = TICK_INTERVAL_NO_TICK; private int mTotalScrollPixels = 0; private boolean mCanPlayLimitFeedback = true; public HapticScrollFeedbackProvider(View view) { this(view, /* rotaryEncoderAxisScrollTickIntervalPixels= */ TICK_INTERVAL_UNSET); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public HapticScrollFeedbackProvider(View view, int rotaryEncoderAxisScrollTickIntervalPixels) { mView = view; mRotaryEncoderAxisScrollTickIntervalPixels = rotaryEncoderAxisScrollTickIntervalPixels; } @Override public void onScrollProgress(MotionEvent event, int axis, int deltaInPixels) { maybeUpdateCurrentConfig(event, axis); if (mTickIntervalPixels == TICK_INTERVAL_NO_TICK) { // There's no valid tick interval. Exit early before doing any further computation. return; } mTotalScrollPixels += deltaInPixels; if (Math.abs(mTotalScrollPixels) >= mTickIntervalPixels) { mTotalScrollPixels %= mTickIntervalPixels; // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here. mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK); } mCanPlayLimitFeedback = true; } @Override public void onScrollLimit(MotionEvent event, int axis, boolean isStart) { maybeUpdateCurrentConfig(event, axis); if (!mCanPlayLimitFeedback) { return; } // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here. mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT); mCanPlayLimitFeedback = false; } @Override public void onSnapToItem(MotionEvent event, int axis) { // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here. mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS); mCanPlayLimitFeedback = true; } private void maybeUpdateCurrentConfig(MotionEvent event, int axis) { int source = event.getSource(); int deviceId = event.getDeviceId(); if (mAxis != axis || mSource != source || mDeviceId != deviceId) { mSource = source; mAxis = axis; mDeviceId = deviceId; mCanPlayLimitFeedback = true; mTotalScrollPixels = 0; calculateTickIntervals(source, axis); } } private void calculateTickIntervals(int source, int axis) { mTickIntervalPixels = TICK_INTERVAL_NO_TICK; if (axis == MotionEvent.AXIS_SCROLL && source == InputDevice.SOURCE_ROTARY_ENCODER) { if (mRotaryEncoderAxisScrollTickIntervalPixels == TICK_INTERVAL_UNSET) { // Value has not been fetched yet. Fetch and cache it. mRotaryEncoderAxisScrollTickIntervalPixels = mView.getContext().getResources().getDimensionPixelSize( config_rotaryEncoderAxisScrollTickInterval); if (mRotaryEncoderAxisScrollTickIntervalPixels < 0) { mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_NO_TICK; } } mTickIntervalPixels = mRotaryEncoderAxisScrollTickIntervalPixels; } } } core/java/android/view/ScrollFeedbackProvider.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /** * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; /** * Interface to represent an entity giving consistent feedback for different events surrounding view * scroll. * * @hide */ public interface ScrollFeedbackProvider { /** * 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. */ void onSnapToItem(MotionEvent event, int axis); /** * 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. */ void onScrollLimit(MotionEvent event, int axis, boolean isStart); /** * 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. */ void onScrollProgress(MotionEvent event, int axis, int deltaInPixels); } core/res/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -2766,6 +2766,11 @@ measured in dips per second. Setting this to -1dp disables rotary encoder fling. --> <dimen name="config_viewMaxRotaryEncoderFlingVelocity">-1dp</dimen> <!-- Tick intervals in dp for rotary encoder scrolls on {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER} devices. A valid tick interval value is a positive value. Setting this to 0dp disables scroll tick. --> <dimen name="config_rotaryEncoderAxisScrollTickInterval">21dp</dimen> <!-- Amount of time in ms the user needs to press the relevant key to bring up the global actions dialog --> <integer name="config_globalActionsKeyTimeout">500</integer> Loading core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -2032,6 +2032,7 @@ <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" /> <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> <java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" /> <java-symbol type="integer" name="config_previousVibrationsDumpLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> Loading Loading
core/java/android/view/HapticFeedbackConstants.java +6 −8 Original line number Diff line number Diff line Loading @@ -129,27 +129,25 @@ public class HapticFeedbackConstants { public static final int REJECT = 17; /** * A haptic effect to provide texture while a rotary input device is being scrolled. * A haptic effect to provide texture while scrolling. * * @hide */ public static final int ROTARY_SCROLL_TICK = 18; public static final int SCROLL_TICK = 18; /** * A haptic effect to signal that a list element has been focused while scrolling using a rotary * input device. * A haptic effect to signal that a list element has been focused while scrolling. * * @hide */ public static final int ROTARY_SCROLL_ITEM_FOCUS = 19; public static final int SCROLL_ITEM_FOCUS = 19; /** * A haptic effect to signal reaching the scrolling limits of a list while scrolling using a * rotary input device. * A haptic effect to signal reaching the scrolling limits of a list while scrolling. * * @hide */ public static final int ROTARY_SCROLL_LIMIT = 20; public static final int SCROLL_LIMIT = 20; /** * The user has toggled a switch or button into the on position. Loading
core/java/android/view/HapticScrollFeedbackProvider.java 0 → 100644 +141 −0 Original line number Diff line number Diff line /** * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; import static com.android.internal.R.dimen.config_rotaryEncoderAxisScrollTickInterval; import com.android.internal.annotations.VisibleForTesting; /** * {@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. * * @hide */ public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider { private static final String TAG = "HapticScrollFeedbackProvider"; private static final int TICK_INTERVAL_NO_TICK = 0; private static final int TICK_INTERVAL_UNSET = Integer.MAX_VALUE; private final View mView; // Info about the cause of the latest scroll event. /** The ID of the {link @InputDevice} that caused the latest scroll event. */ private int mDeviceId = -1; /** The axis on which the latest scroll event happened. */ private int mAxis = -1; /** 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. */ private int mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_UNSET; /** The tick interval corresponding to the current InputDevice/source/axis. */ private int mTickIntervalPixels = TICK_INTERVAL_NO_TICK; private int mTotalScrollPixels = 0; private boolean mCanPlayLimitFeedback = true; public HapticScrollFeedbackProvider(View view) { this(view, /* rotaryEncoderAxisScrollTickIntervalPixels= */ TICK_INTERVAL_UNSET); } /** @hide */ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public HapticScrollFeedbackProvider(View view, int rotaryEncoderAxisScrollTickIntervalPixels) { mView = view; mRotaryEncoderAxisScrollTickIntervalPixels = rotaryEncoderAxisScrollTickIntervalPixels; } @Override public void onScrollProgress(MotionEvent event, int axis, int deltaInPixels) { maybeUpdateCurrentConfig(event, axis); if (mTickIntervalPixels == TICK_INTERVAL_NO_TICK) { // There's no valid tick interval. Exit early before doing any further computation. return; } mTotalScrollPixels += deltaInPixels; if (Math.abs(mTotalScrollPixels) >= mTickIntervalPixels) { mTotalScrollPixels %= mTickIntervalPixels; // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here. mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK); } mCanPlayLimitFeedback = true; } @Override public void onScrollLimit(MotionEvent event, int axis, boolean isStart) { maybeUpdateCurrentConfig(event, axis); if (!mCanPlayLimitFeedback) { return; } // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here. mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT); mCanPlayLimitFeedback = false; } @Override public void onSnapToItem(MotionEvent event, int axis) { // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here. mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS); mCanPlayLimitFeedback = true; } private void maybeUpdateCurrentConfig(MotionEvent event, int axis) { int source = event.getSource(); int deviceId = event.getDeviceId(); if (mAxis != axis || mSource != source || mDeviceId != deviceId) { mSource = source; mAxis = axis; mDeviceId = deviceId; mCanPlayLimitFeedback = true; mTotalScrollPixels = 0; calculateTickIntervals(source, axis); } } private void calculateTickIntervals(int source, int axis) { mTickIntervalPixels = TICK_INTERVAL_NO_TICK; if (axis == MotionEvent.AXIS_SCROLL && source == InputDevice.SOURCE_ROTARY_ENCODER) { if (mRotaryEncoderAxisScrollTickIntervalPixels == TICK_INTERVAL_UNSET) { // Value has not been fetched yet. Fetch and cache it. mRotaryEncoderAxisScrollTickIntervalPixels = mView.getContext().getResources().getDimensionPixelSize( config_rotaryEncoderAxisScrollTickInterval); if (mRotaryEncoderAxisScrollTickIntervalPixels < 0) { mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_NO_TICK; } } mTickIntervalPixels = mRotaryEncoderAxisScrollTickIntervalPixels; } } }
core/java/android/view/ScrollFeedbackProvider.java 0 → 100644 +63 −0 Original line number Diff line number Diff line /** * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view; /** * Interface to represent an entity giving consistent feedback for different events surrounding view * scroll. * * @hide */ public interface ScrollFeedbackProvider { /** * 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. */ void onSnapToItem(MotionEvent event, int axis); /** * 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. */ void onScrollLimit(MotionEvent event, int axis, boolean isStart); /** * 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. */ void onScrollProgress(MotionEvent event, int axis, int deltaInPixels); }
core/res/res/values/config.xml +5 −0 Original line number Diff line number Diff line Loading @@ -2766,6 +2766,11 @@ measured in dips per second. Setting this to -1dp disables rotary encoder fling. --> <dimen name="config_viewMaxRotaryEncoderFlingVelocity">-1dp</dimen> <!-- Tick intervals in dp for rotary encoder scrolls on {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER} devices. A valid tick interval value is a positive value. Setting this to 0dp disables scroll tick. --> <dimen name="config_rotaryEncoderAxisScrollTickInterval">21dp</dimen> <!-- Amount of time in ms the user needs to press the relevant key to bring up the global actions dialog --> <integer name="config_globalActionsKeyTimeout">500</integer> Loading
core/res/res/values/symbols.xml +1 −0 Original line number Diff line number Diff line Loading @@ -2032,6 +2032,7 @@ <java-symbol type="integer" name="config_notificationsBatteryMediumARGB" /> <java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" /> <java-symbol type="integer" name="config_notificationServiceArchiveSize" /> <java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" /> <java-symbol type="integer" name="config_previousVibrationsDumpLimit" /> <java-symbol type="integer" name="config_defaultVibrationAmplitude" /> <java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" /> Loading