Add scroll haptics for AbsListView and ScrollView
Bug: 287914819
Test: manual
Change-Id: I25a930b3305e6ddb34574abecf054241d93ad2e2
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index d554347..cc9fe65 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -29,6 +29,7 @@
":telecom_flags_core_java_lib{.generated_srcjars}",
":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
+ ":android.widget.flags-aconfig-java{.generated_srcjars}",
],
// Add aconfig-annotations-lib as a dependency for the optimization
libs: ["aconfig-annotations-lib"],
@@ -185,3 +186,17 @@
aconfig_declarations: "android.view.flags-aconfig",
defaults: ["framework-minus-apex-aconfig-java-defaults"],
}
+
+// Widget
+aconfig_declarations {
+ name: "android.widget.flags-aconfig",
+ package: "android.widget.flags",
+ srcs: ["core/java/android/widget/flags/*.aconfig"],
+}
+
+java_aconfig_library {
+ name: "android.widget.flags-aconfig-java",
+ aconfig_declarations: "android.widget.flags-aconfig",
+ defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 03364b6..a116542 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -53,6 +53,7 @@
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
+import android.view.HapticScrollFeedbackProvider;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -91,6 +92,7 @@
import android.view.inputmethod.SurroundingText;
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
+import android.widget.flags.Flags;
import android.widget.RemoteViews.InteractionHandler;
import com.android.internal.R;
@@ -918,6 +920,8 @@
private DifferentialMotionFlingHelper mDifferentialMotionFlingHelper;
+ private HapticScrollFeedbackProvider mHapticScrollFeedbackProvider;
+
public AbsListView(Context context) {
super(context);
setupDeviceConfigProperties();
@@ -4502,10 +4506,6 @@
final float axisValue = (axis == -1) ? 0 : event.getAxisValue(axis);
final int delta = Math.round(axisValue * mVerticalScrollFactor);
if (delta != 0) {
- // Tracks whether or not we should attempt fling for this event.
- // Fling should not be attempted if the view is already at the limit of scroll,
- // since it conflicts with EdgeEffect.
- boolean shouldAttemptFling = true;
// If we're moving down, we want the top item. If we're moving up, bottom item.
final int motionIndex = delta > 0 ? 0 : getChildCount() - 1;
@@ -4518,10 +4518,12 @@
final int overscrollMode = getOverScrollMode();
if (!trackMotionScroll(delta, delta)) {
- if (shouldAttemptFling) {
- initDifferentialFlingHelperIfNotExists();
- mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
+ if (Flags.platformWidgetHapticScrollFeedback()) {
+ initHapticScrollFeedbackProviderIfNotExists();
+ mHapticScrollFeedbackProvider.onScrollProgress(event, axis, delta);
}
+ initDifferentialFlingHelperIfNotExists();
+ mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
return true;
} else if (!event.isFromSource(InputDevice.SOURCE_MOUSE) && motionView != null
&& (overscrollMode == OVER_SCROLL_ALWAYS
@@ -4530,7 +4532,13 @@
int motionViewRealTop = motionView.getTop();
float overscroll = (delta - (motionViewRealTop - motionViewPrevTop))
/ ((float) getHeight());
- if (delta > 0) {
+ boolean hitTopLimit = delta > 0;
+ if (Flags.platformWidgetHapticScrollFeedback()) {
+ initHapticScrollFeedbackProviderIfNotExists();
+ mHapticScrollFeedbackProvider.onScrollLimit(
+ event, axis, /* isStart= */ hitTopLimit);
+ }
+ if (hitTopLimit) {
mEdgeGlowTop.onPullDistance(overscroll, 0.5f);
mEdgeGlowTop.onRelease();
} else {
@@ -4696,6 +4704,12 @@
}
}
+ private void initHapticScrollFeedbackProviderIfNotExists() {
+ if (mHapticScrollFeedbackProvider == null) {
+ mHapticScrollFeedbackProvider = new HapticScrollFeedbackProvider(this);
+ }
+ }
+
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index d330ebf..90b077b 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -33,6 +33,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.FocusFinder;
+import android.view.HapticScrollFeedbackProvider;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -47,6 +48,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.inspector.InspectableProperty;
+import android.widget.flags.Flags;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -206,6 +208,8 @@
private DifferentialMotionFlingHelper mDifferentialMotionFlingHelper;
+ private HapticScrollFeedbackProvider mHapticScrollFeedbackProvider;
+
/**
* Sentinel value for no current active pointer.
* Used by {@link #mActivePointerId}.
@@ -604,6 +608,12 @@
}
}
+ private void initHapticScrollFeedbackProviderIfNotExists() {
+ if (mHapticScrollFeedbackProvider == null) {
+ mHapticScrollFeedbackProvider = new HapticScrollFeedbackProvider(this);
+ }
+ }
+
private void recycleVelocityTracker() {
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
@@ -967,7 +977,7 @@
// Tracks whether or not we should attempt fling for this event.
// Fling should not be attempted if the view is already at the limit of scroll,
// since it conflicts with EdgeEffect.
- boolean shouldAttemptFling = true;
+ boolean hitLimit = false;
final int range = getScrollRange();
int oldScrollY = mScrollY;
int newScrollY = oldScrollY - delta;
@@ -986,7 +996,7 @@
absorbed = true;
}
newScrollY = 0;
- shouldAttemptFling = false;
+ hitLimit = true;
} else if (newScrollY > range) {
if (canOverscroll) {
mEdgeGlowBottom.onPullDistance(
@@ -996,11 +1006,21 @@
absorbed = true;
}
newScrollY = range;
- shouldAttemptFling = false;
+ hitLimit = true;
}
if (newScrollY != oldScrollY) {
super.scrollTo(mScrollX, newScrollY);
- if (shouldAttemptFling) {
+ if (hitLimit) {
+ if (Flags.platformWidgetHapticScrollFeedback()) {
+ initHapticScrollFeedbackProviderIfNotExists();
+ mHapticScrollFeedbackProvider.onScrollLimit(
+ event, axis, /* isStart= */ newScrollY == 0);
+ }
+ } else {
+ if (Flags.platformWidgetHapticScrollFeedback()) {
+ initHapticScrollFeedbackProviderIfNotExists();
+ mHapticScrollFeedbackProvider.onScrollProgress(event, axis, delta);
+ }
initDifferentialFlingHelperIfNotExists();
mDifferentialMotionFlingHelper.onMotionEvent(event, axis);
}
diff --git a/core/java/android/widget/flags/scroll_view_flags.aconfig b/core/java/android/widget/flags/scroll_view_flags.aconfig
new file mode 100644
index 0000000..f93ade2
--- /dev/null
+++ b/core/java/android/widget/flags/scroll_view_flags.aconfig
@@ -0,0 +1,8 @@
+package: "android.widget.flags"
+
+flag {
+ namespace: "widget"
+ name: "platform_widget_haptic_scroll_feedback"
+ description: "Enables haptic scroll feedback in platform widgets"
+ bug: "287914819"
+}
\ No newline at end of file