Add nested pre-fling for nested scrolling

Nested pre-fling allows a nested scrolling parent to consume a fling
operation before the child view does. If a parent has been consuming
nested pre-scroll events in a particular direction, this is generally
a good indication that it should also consume the resulting fling at
the end.

Bug 15538504

Change-Id: I88e8753a96c9b41815c3be530cafab8345164e64
diff --git a/api/current.txt b/api/current.txt
index f799671..8a99dac 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -33524,6 +33524,7 @@
     method public boolean dispatchKeyEventPreIme(android.view.KeyEvent);
     method public boolean dispatchKeyShortcutEvent(android.view.KeyEvent);
     method public boolean dispatchNestedFling(float, float, boolean);
+    method public boolean dispatchNestedPreFling(float, float);
     method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
     method public boolean dispatchNestedScroll(int, int, int, int, int[]);
     method public boolean dispatchPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
@@ -34334,6 +34335,7 @@
     method public boolean onInterceptTouchEvent(android.view.MotionEvent);
     method protected abstract void onLayout(boolean, int, int, int, int);
     method public boolean onNestedFling(android.view.View, float, float, boolean);
+    method public boolean onNestedPreFling(android.view.View, float, float);
     method public void onNestedPreScroll(android.view.View, int, int, int[]);
     method public void onNestedScroll(android.view.View, int, int, int, int);
     method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
@@ -34479,6 +34481,7 @@
     method public abstract boolean isTextDirectionResolved();
     method public abstract void notifySubtreeAccessibilityStateChanged(android.view.View, android.view.View, int);
     method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
+    method public abstract boolean onNestedPreFling(android.view.View, float, float);
     method public abstract void onNestedPreScroll(android.view.View, int, int, int[]);
     method public abstract void onNestedScroll(android.view.View, int, int, int, int);
     method public abstract void onNestedScrollAccepted(android.view.View, android.view.View, int);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index a3e2c96..7bc8bc5 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -18445,6 +18445,43 @@
     }
 
     /**
+     * Dispatch a fling to a nested scrolling parent before it is processed by this view.
+     *
+     * <p>Nested pre-fling events are to nested fling events what touch intercept is to touch
+     * and what nested pre-scroll is to nested scroll. <code>dispatchNestedPreFling</code>
+     * offsets an opportunity for the parent view in a nested fling to fully consume the fling
+     * before the child view consumes it. If this method returns <code>true</code>, a nested
+     * parent view consumed the fling and this view should not scroll as a result.</p>
+     *
+     * <p>For a better user experience, only one view in a nested scrolling chain should consume
+     * the fling at a time. If a parent view consumed the fling this method will return false.
+     * Custom view implementations should account for this in two ways:</p>
+     *
+     * <ul>
+     *     <li>If a custom view is paged and needs to settle to a fixed page-point, do not
+     *     call <code>dispatchNestedPreFling</code>; consume the fling and settle to a valid
+     *     position regardless.</li>
+     *     <li>If a nested parent does consume the fling, this view should not scroll at all,
+     *     even to settle back to a valid idle position.</li>
+     * </ul>
+     *
+     * <p>Views should also not offer fling velocities to nested parent views along an axis
+     * where scrolling is not currently supported; a {@link android.widget.ScrollView ScrollView}
+     * should not offer a horizontal fling velocity to its parents since scrolling along that
+     * axis is not permitted and carrying velocity along that motion does not make sense.</p>
+     *
+     * @param velocityX Horizontal fling velocity in pixels per second
+     * @param velocityY Vertical fling velocity in pixels per second
+     * @return true if a nested scrolling parent consumed the fling
+     */
+    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
+        if (isNestedScrollingEnabled() && mNestedScrollingParent != null) {
+            return mNestedScrollingParent.onNestedPreFling(this, velocityX, velocityY);
+        }
+        return false;
+    }
+
+    /**
      * Gets a scale factor that determines the distance the view should scroll
      * vertically in response to {@link MotionEvent#ACTION_SCROLL}.
      * @return The vertical scroll scale factor.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index ac70066..04c8b0b 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -6066,6 +6066,14 @@
     }
 
     /**
+     * @inheritDoc
+     */
+    @Override
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+        return false;
+    }
+
+    /**
      * Return the current axes of nested scrolling for this ViewGroup.
      *
      * <p>A ViewGroup returning something other than {@link #SCROLL_AXIS_NONE} is currently
diff --git a/core/java/android/view/ViewParent.java b/core/java/android/view/ViewParent.java
index 588b9cd..87a37f4 100644
--- a/core/java/android/view/ViewParent.java
+++ b/core/java/android/view/ViewParent.java
@@ -523,10 +523,32 @@
      * parent instead. The parent may optionally consume the fling or observe a child fling.</p>
      *
      * @param target View that initiated the nested scroll
-     * @param velocityX Horizontal velocity in pixels per second.
+     * @param velocityX Horizontal velocity in pixels per second
      * @param velocityY Vertical velocity in pixels per second
      * @param consumed true if the child consumed the fling, false otherwise
      * @return true if this parent consumed or otherwise reacted to the fling
      */
     public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed);
+
+    /**
+     * React to a nested fling before the target view consumes it.
+     *
+     * <p>This method siginfies that a nested scrolling child has detected a fling with the given
+     * velocity along each axis. Generally this means that a touch scroll has ended with a
+     * {@link VelocityTracker velocity} in the direction of scrolling that meets or exceeds
+     * the {@link ViewConfiguration#getScaledMinimumFlingVelocity() minimum fling velocity}
+     * along a scrollable axis.</p>
+     *
+     * <p>If a nested scrolling parent is consuming motion as part of a
+     * {@link #onNestedPreScroll(View, int, int, int[]) pre-scroll}, it may be appropriate for
+     * it to also consume the pre-fling to complete that same motion. By returning
+     * <code>true</code> from this method, the parent indicates that the child should not
+     * fling its own internal content as well.</p>
+     *
+     * @param target View that initiated the nested scroll
+     * @param velocityX Horizontal velocity in pixels per second
+     * @param velocityY Vertical velocity in pixels per second
+     * @return true if this parent consumed the fling ahead of the target view
+     */
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY);
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index a8d3f99..d22415e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -6291,6 +6291,11 @@
         return false;
     }
 
+    @Override
+    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
+        return false;
+    }
+
     void changeCanvasOpacity(boolean opaque) {
         Log.d(TAG, "changeCanvasOpacity: opaque=" + opaque);
         if (mAttachInfo.mHardwareRenderer != null) {