am 61f13cd8: am ae657a05: am e23f97ba: Doc change: updates to Gestures training class.

* commit '61f13cd877405f8bb72c2ad33c717ba3f02acf74':
  Doc change: updates to Gestures training class.
diff --git a/docs/downloads/training/InteractiveChart.zip b/docs/downloads/training/InteractiveChart.zip
new file mode 100644
index 0000000..95248ad
--- /dev/null
+++ b/docs/downloads/training/InteractiveChart.zip
Binary files differ
diff --git a/docs/html/training/gestures/detector.jd b/docs/html/training/gestures/detector.jd
index 06d0e98..65ddb1b 100644
--- a/docs/html/training/gestures/detector.jd
+++ b/docs/html/training/gestures/detector.jd
@@ -25,12 +25,18 @@
    <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/index.jd b/docs/html/training/gestures/index.jd
index 0191450..16ca7b0 100644
--- a/docs/html/training/gestures/index.jd
+++ b/docs/html/training/gestures/index.jd
@@ -20,12 +20,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/movement.jd b/docs/html/training/gestures/movement.jd
index f2c49d7..fdc1ea4 100644
--- a/docs/html/training/gestures/movement.jd
+++ b/docs/html/training/gestures/movement.jd
@@ -24,12 +24,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/multi.jd b/docs/html/training/gestures/multi.jd
index d4c5b1d..6a0df11 100644
--- a/docs/html/training/gestures/multi.jd
+++ b/docs/html/training/gestures/multi.jd
@@ -25,12 +25,18 @@
    <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
diff --git a/docs/html/training/gestures/scale.jd b/docs/html/training/gestures/scale.jd
index 17e4085..f2e4eb8 100644
--- a/docs/html/training/gestures/scale.jd
+++ b/docs/html/training/gestures/scale.jd
@@ -15,6 +15,7 @@
 <h2>This lesson teaches you to</h2>
 <ol>
   <li><a href="#drag">Drag an Object</a></li>
+  <li><a href="#pan">Drag to Pan</a></li>
   <li><a href="#scale">Use Touch to Perform Scaling</a></li>
 </ol>
 
@@ -25,20 +26,25 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
+
 <p>This lesson describes how to use touch gestures to drag and scale on-screen
 objects, using {@link android.view.View#onTouchEvent onTouchEvent()} to intercept
-touch events. Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples used in this lesson.
+touch events. 
 </p>
 
 <h2 id="drag">Drag an Object</h2>
@@ -128,17 +134,15 @@
         final float x = MotionEventCompat.getX(ev, pointerIndex);
         final float y = MotionEventCompat.getY(ev, pointerIndex);
             
-        // Only move if the ScaleGestureDetector isn't processing a gesture.
-        if (!mScaleDetector.isInProgress()) {
-            // Calculate the distance moved
-            final float dx = x - mLastTouchX;
-            final float dy = y - mLastTouchY;
+        // Calculate the distance moved
+        final float dx = x - mLastTouchX;
+        final float dy = y - mLastTouchY;
 
-            mPosX += dx;
-            mPosY += dy;
+        mPosX += dx;
+        mPosY += dy;
 
-            invalidate();
-        }
+        invalidate();
+
         // Remember this touch position for the next move event
         mLastTouchX = x;
         mLastTouchY = y;
@@ -175,6 +179,88 @@
     return true;
 }</pre>
 
+<h2 id="pan">Drag to Pan</h2>
+
+<p>The previous section showed an example of dragging an object around the screen. Another 
+common scenario is <em>panning</em>, which is when a user's dragging motion causes scrolling 
+in both the x and y axes. The above snippet directly intercepted the {@link android.view.MotionEvent} 
+actions to implement dragging. The snippet in this section takes advantage of the platform's 
+built-in support for common gestures. It overrides 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
+{@link android.view.GestureDetector.SimpleOnGestureListener}.</p>
+
+<p>To provide a little more context, {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
+is called when a user is dragging his finger to pan the content. 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} is only called when 
+a finger is down; as soon as the finger is lifted from the screen, the gesture either ends, 
+or a fling gesture is started (if the finger was moving with some speed just before it was lifted). 
+For more discussion of scrolling vs. flinging, see <a href="scroll.html">Animating a Scroll Gesture</a>.</p>
+
+<p>Here is the snippet for {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}:
+
+
+<pre>// The current viewport. This rectangle represents the currently visible 
+// chart domain and range. 
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the 
+// chart data should be drawn.
+private Rect mContentRect;
+
+private final GestureDetector.SimpleOnGestureListener mGestureListener
+            = new GestureDetector.SimpleOnGestureListener() {
+...
+
+&#64;Override
+public boolean onScroll(MotionEvent e1, MotionEvent e2, 
+            float distanceX, float distanceY) {
+    // Scrolling uses math based on the viewport (as opposed to math using pixels).
+    
+    // Pixel offset is the offset in screen pixels, while viewport offset is the
+    // offset within the current viewport. 
+    float viewportOffsetX = distanceX * mCurrentViewport.width() 
+            / mContentRect.width();
+    float viewportOffsetY = -distanceY * mCurrentViewport.height() 
+            / mContentRect.height();
+    ...
+    // Updates the viewport, refreshes the display. 
+    setViewportBottomLeft(
+            mCurrentViewport.left + viewportOffsetX,
+            mCurrentViewport.bottom + viewportOffsetY);
+    ...
+    return true;
+}</pre>
+
+<p>The implementation of {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
+scrolls the viewport in response to the touch gesture:</p>
+
+<pre>
+/**
+ * Sets the current viewport (defined by mCurrentViewport) to the given
+ * X and Y positions. Note that the Y value represents the topmost pixel position, 
+ * and thus the bottom of the mCurrentViewport rectangle.
+ */
+private void setViewportBottomLeft(float x, float y) {
+    /*
+     * Constrains within the scroll range. The scroll range is simply the viewport 
+     * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the 
+     * extremes were 0 and 10, and the viewport size was 2, the scroll range would 
+     * be 0 to 8.
+     */
+
+    float curWidth = mCurrentViewport.width();
+    float curHeight = mCurrentViewport.height();
+    x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth));
+    y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX));
+
+    mCurrentViewport.set(x, y - curHeight, x + curWidth, y);
+
+    // Invalidates the View to update the display.
+    ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
 <h2 id="scale">Use Touch to Perform Scaling</h2>
 
 <p>As discussed in <a href="detector.html">Detecting Common Gestures</a>,
@@ -191,10 +277,10 @@
 {@link android.view.ScaleGestureDetector.SimpleOnScaleGestureListener} 
 as a helper class that you can extend if you don’t care about all of the reported events.</p>
 
-<p>Here is a snippet that gives you the basic idea of how to perform scaling.
-Here is the original <a
-href="http://code.google.com/p/android-touchexample/">source code</a>
-for the examples.</p>
+
+<h3>Basic scaling example</h3>
+
+<p>Here is a snippet that illustrates the basic ingredients involved in scaling.</p>
 
 <pre>private ScaleGestureDetector mScaleDetector;
 private float mScaleFactor = 1.f;
@@ -238,3 +324,88 @@
         return true;
     }
 }</pre>
+
+
+
+
+<h3>More complex scaling example</h3>
+<p>Here is a more complex example from the {@code InteractiveChart} sample provided with this class. 
+The {@code InteractiveChart} sample supports both scrolling (panning) and scaling with multiple fingers,
+using the {@link android.view.ScaleGestureDetector} "span" 
+({@link android.view.ScaleGestureDetector#getCurrentSpanX getCurrentSpanX/Y}) and 
+"focus" ({@link android.view.ScaleGestureDetector#getFocusX getFocusX/Y}) features:</p>
+
+<pre>&#64;Override
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+private Rect mContentRect;
+private ScaleGestureDetector mScaleGestureDetector;
+...
+public boolean onTouchEvent(MotionEvent event) {
+    boolean retVal = mScaleGestureDetector.onTouchEvent(event);
+    retVal = mGestureDetector.onTouchEvent(event) || retVal;
+    return retVal || super.onTouchEvent(event);
+}
+
+/**
+ * The scale listener, used for handling multi-finger scale gestures.
+ */
+private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener
+        = new ScaleGestureDetector.SimpleOnScaleGestureListener() {
+    /**
+     * This is the active focal point in terms of the viewport. Could be a local
+     * variable but kept here to minimize per-frame allocations.
+     */
+    private PointF viewportFocus = new PointF();
+    private float lastSpanX;
+    private float lastSpanY;
+
+    // Detects that new pointers are going down.
+    &#64;Override
+    public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
+        lastSpanX = ScaleGestureDetectorCompat.
+                getCurrentSpanX(scaleGestureDetector);
+        lastSpanY = ScaleGestureDetectorCompat.
+                getCurrentSpanY(scaleGestureDetector);
+        return true;
+    }
+
+    &#64;Override
+    public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
+
+        float spanX = ScaleGestureDetectorCompat.
+                getCurrentSpanX(scaleGestureDetector);
+        float spanY = ScaleGestureDetectorCompat.
+                getCurrentSpanY(scaleGestureDetector);
+
+        float newWidth = lastSpanX / spanX * mCurrentViewport.width();
+        float newHeight = lastSpanY / spanY * mCurrentViewport.height();
+
+        float focusX = scaleGestureDetector.getFocusX();
+        float focusY = scaleGestureDetector.getFocusY();
+        // Makes sure that the chart point is within the chart region.
+        // See the sample for the implementation of hitTest().
+        hitTest(scaleGestureDetector.getFocusX(),
+                scaleGestureDetector.getFocusY(),
+                viewportFocus);
+
+        mCurrentViewport.set(
+                viewportFocus.x
+                        - newWidth * (focusX - mContentRect.left)
+                        / mContentRect.width(),
+                viewportFocus.y
+                        - newHeight * (mContentRect.bottom - focusY)
+                        / mContentRect.height(),
+                0,
+                0);
+        mCurrentViewport.right = mCurrentViewport.left + newWidth;
+        mCurrentViewport.bottom = mCurrentViewport.top + newHeight;     
+        ...
+        // Invalidates the View to update the display.
+        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
+
+        lastSpanX = spanX;
+        lastSpanY = spanY;
+        return true;
+    }
+};</pre>
diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd
index 8576948..bd1537a 100644
--- a/docs/html/training/gestures/scroll.jd
+++ b/docs/html/training/gestures/scroll.jd
@@ -14,6 +14,7 @@
 <!-- table of contents -->
 <h2>This lesson teaches you to</h2>
 <ol>
+  <li><a href="#term">Understand Scrolling Terminology</a></li>
   <li><a href="#scroll">Implement Touch-Based Scrolling</a></li>
 </ol>
 
@@ -24,12 +25,18 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
 
 </div>
 </div>
@@ -45,7 +52,26 @@
 
 <p>You can use scrollers ({@link android.widget.Scroller} or {@link
 android.widget.OverScroller}) to collect the data you need to produce a
-scrolling animation in response to a touch event.</p>
+scrolling animation in response to a touch event. They are similar, but
+{@link android.widget.OverScroller}
+includes methods for indicating to users that they've reached the content edges 
+after a pan or fling gesture. The {@code InteractiveChart} sample 
+uses the the {@link android.widget.EdgeEffect} class 
+(actually the {@link android.support.v4.widget.EdgeEffectCompat} class)
+to display a "glow" effect when users reach the content edges.</p>
+
+<p class="note"><strong>Note:</strong> We recommend that you
+use {@link android.widget.OverScroller} rather than {@link
+android.widget.Scroller} for scrolling animations.
+{@link android.widget.OverScroller} provides the best backward
+compatibility with older devices.
+<br />
+Also note that you generally only need to use scrollers
+when implementing scrolling yourself. {@link android.widget.ScrollView} and
+{@link android.widget.HorizontalScrollView} do all of this for you if you nest your 
+layout within them.
+</p>
+
 
 <p>A scroller is used  to animate scrolling over time, using platform-standard
 scrolling physics (friction, velocity, etc.). The scroller itself doesn't
@@ -54,101 +80,280 @@
 responsibility to get and apply new coordinates at a rate that will make the
 scrolling animation look smooth.</p>
 
-<p class="note"><strong>Note:</strong> You generally only need to use scrollers
-when implementing scrolling yourself. {@link android.widget.ScrollView} and
-{@link android.widget.HorizontalScrollView} do all this for you do all of this for you if you nest your layout within them.</p>
-
-<h2 id = "scroll">Implement Touch-Based Scrolling</h2>
 
 
-<p>This snippet illustrates the basics of using a scroller. It uses a 
-{@link android.view.GestureDetector}, and overrides the  
-{@link android.view.GestureDetector.SimpleOnGestureListener} methods 
-{@link android.view.GestureDetector.OnGestureListener#onDown onDown()} and 
-{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. It also 
-overrides {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} 
-to return {@code false} since you don't need to animate a scroll.</p>
+<h2 id="term">Understand Scrolling Terminology</h2>
 
+<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p>
 
-<p>It's common to use scrollers in conjunction with a fling gesture, but they
+<p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window' 
+of content you're looking at). When scrolling is in both the x and y axes, it's called 
+<em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates 
+two different types of scrolling, dragging and flinging:</p>
+<ul>
+    <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her 
+finger across the touch screen. Simple dragging is often implemented by overriding 
+{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in 
+{@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see 
+<a href="dragging.jd">Dragging and Scaling</a>.</li>
+
+    <li><strong>Flinging</strong> is the type of scrolling that occurs when a user 
+drags and lifts her finger quickly. After the user lifts her finger, you generally 
+want to keep scrolling (moving the viewport), but decelerate until the viewport stops moving. 
+Flinging can be implemented by overriding 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()} 
+in {@link android.view.GestureDetector.OnGestureListener}, and by using 
+a scroller object. This is the use 
+case that is the topic of this lesson.</li>
+</ul>
+
+<p>It's common to use scroller objects 
+in conjunction with a fling gesture, but they
 can be used in pretty much any context where you want the UI to display
-scrolling in response to a touch event. For example, you could override {@link
-android.view.View#onTouchEvent onTouchEvent()} to process touch events directly,
-and produce a scrolling effect in response to those touch events.</p>
+scrolling in response to a touch event. For example, you could override  
+{@link android.view.View#onTouchEvent onTouchEvent()} to process touch 
+events directly, and produce a scrolling effect or a "snapping to page" animation 
+in response to those touch events.</p>
 
-<pre>
-private OverScroller mScroller = new OverScroller(context);
 
-private GestureDetector.SimpleOnGestureListener mGestureListener
+<h2 id="#scroll">Implement Touch-Based Scrolling</h2> 
+
+<p>This section describes how to use a scroller.
+The snippet shown below comes from the {@code InteractiveChart} sample 
+provided with this class.
+It uses a 
+{@link android.view.GestureDetector}, and overrides the  
+{@link android.view.GestureDetector.SimpleOnGestureListener} method 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}.
+It uses {@link android.widget.OverScroller} to track the fling gesture.
+If the user reaches the content edges 
+after the fling gesture, the app displays a "glow" effect.
+</p>
+
+<p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a 
+chart that you can zoom, pan, scroll, and so on. In the following snippet, 
+{@code mContentRect} represents the rectangle coordinates within the view that the chart 
+will be drawn into. At any given time, a subset of the total chart domain and range are drawn 
+into this rectangular area. 
+{@code mCurrentViewport} represents the portion of the chart that is currently 
+visible in the screen. Because pixel offsets are generally treated as integers, 
+{@code mContentRect} is of the type {@link android.graphics.Rect}. Because the 
+graph domain and range are decimal/float values, {@code mCurrentViewport} is of 
+the type {@link android.graphics.RectF}.</p>
+
+<p>The first part of the snippet shows the implementation of 
+{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p>
+
+<pre>// The current viewport. This rectangle represents the currently visible 
+// chart domain and range. The viewport is the part of the app that the
+// user manipulates via touch gestures.
+private RectF mCurrentViewport = 
+        new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX);
+
+// The current destination rectangle (in pixel coordinates) into which the 
+// chart data should be drawn.
+private Rect mContentRect;
+
+private OverScroller mScroller;
+private RectF mScrollerStartViewport;
+...
+private final GestureDetector.SimpleOnGestureListener mGestureListener
         = new GestureDetector.SimpleOnGestureListener() {
     &#64;Override
     public boolean onDown(MotionEvent e) {
-        // Abort any active scroll animations and invalidate.
+        // Initiates the decay phase of any active edge effects.
+        releaseEdgeEffects();
+        mScrollerStartViewport.set(mCurrentViewport);
+        // Aborts any active scroll animations and invalidates.
         mScroller.forceFinished(true);
-        // There is also a compatibility version: 
-        // ViewCompat.postInvalidateOnAnimation
-        postInvalidateOnAnimation();
+        ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this);
         return true;
     }
-
-    &#64;Override
-    public boolean onScroll(MotionEvent e1, MotionEvent e2, 
-            float distanceX, float distanceY) {
-        // You don't use a scroller in onScroll because you don't need to animate
-        // a scroll. The scroll occurs instantly in response to touch feedback.
-        return false;
-    }
-
+    ...
     &#64;Override
     public boolean onFling(MotionEvent e1, MotionEvent e2, 
             float velocityX, float velocityY) {
-        // Before flinging, abort the current animation.
-        mScroller.forceFinished(true);
-        // Begin the scroll animation
-        mScroller.fling(
-                // Current scroll position
-                startX,
-                startY,
-                // Velocities, negated for natural touch response
-                (int) -velocityX,
-                (int) -velocityY,
-                // Minimum and maximum scroll positions. The minimum scroll 
-                // position is generally zero and the maximum scroll position 
-                // is generally the content size less the screen size. So if the 
-                // content width is 1000 pixels and the screen width is 200  
-                // pixels, the maximum scroll offset should be 800 pixels.
-                minX, maxX,
-                minY, maxY,
-                // The maximum overscroll bounds. This is useful when using
-                // the EdgeEffect class to draw overscroll "glow" overlays.
-                mContentRect.width() / 2,
-                mContentRect.height() / 2);
-        // Invalidate to trigger computeScroll()
-        postInvalidateOnAnimation();
+        fling((int) -velocityX, (int) -velocityY);
         return true;
     }
 };
 
+private void fling(int velocityX, int velocityY) {
+    // Initiates the decay phase of any active edge effects.
+    releaseEdgeEffects();
+    // Flings use math in pixels (as opposed to math based on the viewport).
+    Point surfaceSize = computeScrollSurfaceSize();
+    mScrollerStartViewport.set(mCurrentViewport);
+    int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - 
+            AXIS_X_MIN) / (
+            AXIS_X_MAX - AXIS_X_MIN));
+    int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - 
+            mScrollerStartViewport.bottom) / (
+            AXIS_Y_MAX - AXIS_Y_MIN));
+    // Before flinging, aborts the current animation.
+    mScroller.forceFinished(true);
+    // Begins the animation
+    mScroller.fling(
+            // Current scroll position
+            startX,
+            startY,
+            velocityX,
+            velocityY,
+            /*
+             * Minimum and maximum scroll positions. The minimum scroll 
+             * position is generally zero and the maximum scroll position 
+             * is generally the content size less the screen size. So if the 
+             * content width is 1000 pixels and the screen width is 200  
+             * pixels, the maximum scroll offset should be 800 pixels.
+             */
+            0, surfaceSize.x - mContentRect.width(),
+            0, surfaceSize.y - mContentRect.height(),
+            // The edges of the content. This comes into play when using
+            // the EdgeEffect class to draw "glow" overlays.
+            mContentRect.width() / 2,
+            mContentRect.height() / 2);
+    // Invalidates to trigger computeScroll()
+    ViewCompat.postInvalidateOnAnimation(this);
+}</pre>
+
+<p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls 
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}, 
+it triggers 
+{@link android.view.View#computeScroll computeScroll()} to update the values for x and y. 
+This is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p>
+
+<p>Most views pass the scroller object's x and y position directly to 
+{@link android.view.View#scrollTo scrollTo()}. 
+The following implementation of {@link android.view.View#computeScroll computeScroll()} 
+takes a different approach&mdash;it calls 
+{@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current 
+location of x and y. When the criteria for displaying an overscroll "glow" edge effect are met 
+(the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll), 
+the code sets up the overscroll glow effect and calls 
+{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()} 
+to trigger an invalidate on the view:</p>
+
+<pre>// Edge effect / overscroll tracking objects.
+private EdgeEffectCompat mEdgeEffectTop;
+private EdgeEffectCompat mEdgeEffectBottom;
+private EdgeEffectCompat mEdgeEffectLeft;
+private EdgeEffectCompat mEdgeEffectRight;
+
+private boolean mEdgeEffectTopActive;
+private boolean mEdgeEffectBottomActive;
+private boolean mEdgeEffectLeftActive;
+private boolean mEdgeEffectRightActive;
+
 &#64;Override
 public void computeScroll() {
     super.computeScroll();
 
-    // Compute the current scroll offsets. If this returns true, then the 
-    // scroll has not yet finished.
+    boolean needsInvalidate = false;
+
+    // The scroller isn't finished, meaning a fling or programmatic pan 
+    // operation is currently active.
     if (mScroller.computeScrollOffset()) {
+        Point surfaceSize = computeScrollSurfaceSize();
         int currX = mScroller.getCurrX();
         int currY = mScroller.getCurrY();
 
-        // Actually render the scrolled viewport, or actually scroll the 
-        // view using View.scrollTo.
+        boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN
+                || mCurrentViewport.right < AXIS_X_MAX);
+        boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN
+                || mCurrentViewport.bottom < AXIS_Y_MAX);
 
-        // If currX or currY are outside the bounds, render the overscroll 
-        // glow using EdgeEffect.
+        /*          
+         * If you are zoomed in and currX or currY is
+         * outside of bounds and you're not already
+         * showing overscroll, then render the overscroll
+         * glow edge effect.
+         */
+        if (canScrollX
+                && currX < 0
+                && mEdgeEffectLeft.isFinished()
+                && !mEdgeEffectLeftActive) {
+            mEdgeEffectLeft.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectLeftActive = true;
+            needsInvalidate = true;
+        } else if (canScrollX
+                && currX > (surfaceSize.x - mContentRect.width())
+                && mEdgeEffectRight.isFinished()
+                && !mEdgeEffectRightActive) {
+            mEdgeEffectRight.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectRightActive = true;
+            needsInvalidate = true;
+        }
 
-    } else {
-        // The scroll has finished.
-    }
+        if (canScrollY
+                && currY < 0
+                && mEdgeEffectTop.isFinished()
+                && !mEdgeEffectTopActive) {
+            mEdgeEffectTop.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectTopActive = true;
+            needsInvalidate = true;
+        } else if (canScrollY
+                && currY > (surfaceSize.y - mContentRect.height())
+                && mEdgeEffectBottom.isFinished()
+                && !mEdgeEffectBottomActive) {
+            mEdgeEffectBottom.onAbsorb((int) 
+                    OverScrollerCompat.getCurrVelocity(mScroller));
+            mEdgeEffectBottomActive = true;
+            needsInvalidate = true;
+        }
+        ...
+    }</pre>
+
+<p>Here is the section of the code that performs the actual zoom:</p>
+
+<pre>// Custom object that is functionally similar to Scroller
+Zoomer mZoomer;
+private PointF mZoomFocalPoint = new PointF();
+...
+
+// If a zoom is in progress (either programmatically or via double
+// touch), performs the zoom.
+if (mZoomer.computeZoom()) {
+    float newWidth = (1f - mZoomer.getCurrZoom()) * 
+            mScrollerStartViewport.width();
+    float newHeight = (1f - mZoomer.getCurrZoom()) * 
+            mScrollerStartViewport.height();
+    float pointWithinViewportX = (mZoomFocalPoint.x - 
+            mScrollerStartViewport.left)
+            / mScrollerStartViewport.width();
+    float pointWithinViewportY = (mZoomFocalPoint.y - 
+            mScrollerStartViewport.top)
+            / mScrollerStartViewport.height();
+    mCurrentViewport.set(
+            mZoomFocalPoint.x - newWidth * pointWithinViewportX,
+            mZoomFocalPoint.y - newHeight * pointWithinViewportY,
+            mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX),
+            mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY));
+    constrainViewport();
+    needsInvalidate = true;
+}
+if (needsInvalidate) {
+    ViewCompat.postInvalidateOnAnimation(this);
+}
+</pre>
+
+<p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It 
+computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible, 
+this is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions, 
+the returned size will be twice as large horizontally and vertically.</p>
+
+<pre>private Point computeScrollSurfaceSize() {
+    return new Point(
+            (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN)
+                    / mCurrentViewport.width()),
+            (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN)
+                    / mCurrentViewport.height()));
 }</pre>
 
-<p>For another example of scroller usage, see the <a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 
-{@link android.support.v4.view.ViewPager} class.</p>
+<p>For another example of scroller usage, see the 
+<a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the 
+{@link android.support.v4.view.ViewPager} class. It scrolls in response to flings, 
+and uses scrolling to implement the "snapping to page" animation.</p>
+
diff --git a/docs/html/training/gestures/viewgroup.jd b/docs/html/training/gestures/viewgroup.jd
index 257a5d8..5b32300 100644
--- a/docs/html/training/gestures/viewgroup.jd
+++ b/docs/html/training/gestures/viewgroup.jd
@@ -26,12 +26,19 @@
     <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
     </li>
     <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
-    <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li>
     <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
     <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
     <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
 </ul>
 
+<h2>Try it out</h2>
+
+<div class="download-box">
+  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
+class="button">Download the sample</a>
+ <p class="filename">InteractiveChart.zip</p>
+</div>
+
 
 </div>
 </div>