ScrollView and HorizontalScrollView now will spring back if smoothScrollBy scrolls out of bounds. Tweaked bounce physics for OverScroller.
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 4cc3b9e..8c39231 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -815,9 +815,16 @@
      * @param dy the number of pixels to scroll by on the Y axis
      */
     public final void smoothScrollBy(int dx, int dy) {
+        if (getChildCount() == 0) {
+            // Nothing to do.
+            return;
+        }
         long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
         if (duration > ANIMATED_SCROLL_GAP) {
-            mScroller.startScroll(mScrollX, mScrollY, dx, dy);
+            int width = getWidth() - mPaddingRight - mPaddingLeft;
+            int right = getChildAt(0).getWidth();
+            mScroller.startScroll(mScrollX, mScrollY, dx, dy,
+                    0, Math.max(0, right - width), 0, 0);
             awakenScrollBars(mScroller.getDuration());
             invalidate();
         } else {
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 3fd5dcc..b22ae3c 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -49,7 +49,7 @@
     
     public OverScroller(Context context) {
         mDefaultScroller = new Scroller(context);
-        mDecelScroller = new Scroller(context, new DecelerateInterpolator(3.f));
+        mDecelScroller = new Scroller(context, new DecelerateInterpolator());
         mAccelDecelScroller = new Scroller(context, new AccelerateDecelerateInterpolator());
         mCurrScroller = mDefaultScroller;
     }
@@ -216,7 +216,7 @@
     /**
      * Start scrolling by providing a starting point and the distance to travel.
      * The scroll will use the default value of 250 milliseconds for the
-     * duration.
+     * duration. This version does not spring back to boundaries.
      * 
      * @param startX Starting horizontal scroll offset in pixels. Positive
      *        numbers will scroll the content to the left.
@@ -228,13 +228,45 @@
      *        content up.
      */
     public void startScroll(int startX, int startY, int dx, int dy) {
+        final int minX = Math.min(startX, startX + dx);
+        final int maxX = Math.max(startX, startX + dx);
+        final int minY = Math.min(startY, startY + dy);
+        final int maxY = Math.max(startY, startY + dy);
+        startScroll(startX, startY, dx, dy, minX, maxX, minY, maxY);
+    }
+    
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     * The scroll will use the default value of 250 milliseconds for the
+     * duration. This version will spring back to the provided boundaries if
+     * the scroll value would take it too far.
+     * 
+     * @param startX Starting horizontal scroll offset in pixels. Positive
+     *        numbers will scroll the content to the left.
+     * @param startY Starting vertical scroll offset in pixels. Positive numbers
+     *        will scroll the content up.
+     * @param dx Horizontal distance to travel. Positive numbers will scroll the
+     *        content to the left.
+     * @param dy Vertical distance to travel. Positive numbers will scroll the
+     *        content up.
+     * @param minX Minimum X value. The scroller will not scroll past this
+     *        point.
+     * @param maxX Maximum X value. The scroller will not scroll past this
+     *        point.
+     * @param minY Minimum Y value. The scroller will not scroll past this
+     *        point.
+     * @param maxY Maximum Y value. The scroller will not scroll past this
+     *        point.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy,
+            int minX, int maxX, int minY, int maxY) {
         mCurrScroller.abortAnimation();
         mCurrScroller = mDefaultScroller;
         mScrollMode = MODE_DEFAULT;
-        mMinimumX = Math.min(startX, startX + dx);
-        mMinimumY = Math.min(startY, startY + dy);
-        mMaximumX = Math.max(startX, startX + dx);
-        mMaximumY = Math.max(startY, startY + dy);
+        mMinimumX = minX;
+        mMaximumX = maxX; 
+        mMinimumY = minY;
+        mMaximumY = maxY;
         mCurrScroller.startScroll(startX, startY, dx, dy);
     }
 
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 62797f3..1a50f85 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -817,9 +817,16 @@
      * @param dy the number of pixels to scroll by on the Y axis
      */
     public final void smoothScrollBy(int dx, int dy) {
+        if (getChildCount() == 0) {
+            // Nothing to do.
+            return;
+        }
         long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll;
         if (duration > ANIMATED_SCROLL_GAP) {
-            mScroller.startScroll(mScrollX, mScrollY, dx, dy);
+            int height = getHeight() - mPaddingBottom - mPaddingTop;
+            int bottom = getChildAt(0).getHeight();
+            mScroller.startScroll(mScrollX, mScrollY, dx, dy,
+                    0, 0, 0, Math.max(0, bottom - height));
             awakenScrollBars(mScroller.getDuration());
             invalidate();
         } else {