Added overscroll headers and footers to ListView.

These let developers set a drawable for the list header and footer
to be drawn above and below list content.

Change-Id: Ideddec854cb0bc11f83efb3c000c217844be82c7
diff --git a/api/current.xml b/api/current.xml
index a20997b..d792a41 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -5971,6 +5971,28 @@
  visibility="public"
 >
 </field>
+<field name="overscrollFooter"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843456"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="overscrollHeader"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843455"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="overscrollMode"
  type="int"
  transient="false"
@@ -204557,6 +204579,28 @@
  visibility="public"
 >
 </method>
+<method name="getOverscrollFooter"
+ return="android.graphics.drawable.Drawable"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getOverscrollHeader"
+ return="android.graphics.drawable.Drawable"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isItemChecked"
  return="boolean"
  abstract="false"
@@ -204702,6 +204746,32 @@
 <parameter name="itemsCanFocus" type="boolean">
 </parameter>
 </method>
+<method name="setOverscrollFooter"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="footer" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
+<method name="setOverscrollHeader"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="header" type="android.graphics.drawable.Drawable">
+</parameter>
+</method>
 <method name="setSelection"
  return="void"
  abstract="false"
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index d4552e3..0594844 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -117,6 +117,9 @@
 
     Drawable mDivider;
     int mDividerHeight;
+    
+    Drawable mOverscrollHeader;
+    Drawable mOverscrollFooter;
 
     private boolean mIsCacheColorOpaque;
     private boolean mDividerIsOpaque;
@@ -171,6 +174,16 @@
             // If a divider is specified use its intrinsic height for divider height
             setDivider(d);
         }
+        
+        final Drawable osHeader = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollHeader);
+        if (osHeader != null) {
+            setOverscrollHeader(osHeader);
+        }
+        
+        final Drawable osFooter = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollFooter);
+        if (osFooter != null) {
+            setOverscrollFooter(osFooter);
+        }
 
         // Use the height specified, zero being the default
         final int dividerHeight = a.getDimensionPixelSize(
@@ -2934,12 +2947,51 @@
         super.setCacheColorHint(color);
     }
 
+    void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) {
+        final int height = drawable.getMinimumHeight();
+
+        canvas.save();
+        canvas.clipRect(bounds);
+
+        final int span = bounds.bottom - bounds.top;
+        if (span < height) {
+            bounds.top = bounds.bottom - height; 
+        }
+
+        drawable.setBounds(bounds);
+        drawable.draw(canvas);
+
+        canvas.restore();
+    }
+
+    void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) {
+        final int height = drawable.getMinimumHeight();
+
+        canvas.save();
+        canvas.clipRect(bounds);
+
+        final int span = bounds.bottom - bounds.top;
+        if (span < height) {
+            bounds.bottom = bounds.top + height;
+        }
+
+        drawable.setBounds(bounds);
+        drawable.draw(canvas);
+
+        canvas.restore();
+    }
+
     @Override
     protected void dispatchDraw(Canvas canvas) {
         // Draw the dividers
         final int dividerHeight = mDividerHeight;
+        final Drawable overscrollHeader = mOverscrollHeader;
+        final Drawable overscrollFooter = mOverscrollFooter;
+        final boolean drawOverscrollHeader = overscrollHeader != null;
+        final boolean drawOverscrollFooter = overscrollFooter != null;
+        final boolean drawDividers = dividerHeight > 0 && mDivider != null;
 
-        if (dividerHeight > 0 && mDivider != null) {
+        if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) {
             // Only modify the top and bottom in the loop, we set the left and right here
             final Rect bounds = mTempRect;
             bounds.left = mPaddingLeft;
@@ -2947,7 +2999,8 @@
 
             final int count = getChildCount();
             final int headerCount = mHeaderViewInfos.size();
-            final int footerLimit = mItemCount - mFooterViewInfos.size() - 1;
+            final int itemCount = mItemCount;
+            final int footerLimit = itemCount - mFooterViewInfos.size() - 1;
             final boolean headerDividers = mHeaderDividersEnabled;
             final boolean footerDividers = mFooterDividersEnabled;
             final int first = mFirstPosition;
@@ -2957,7 +3010,7 @@
             // fill a rect where the dividers would be for non-selectable items
             // If the list is opaque and the background is also opaque, we don't
             // need to draw anything since the background will do it for us
-            final boolean fillForMissingDividers = isOpaque() && !super.isOpaque();
+            final boolean fillForMissingDividers = drawDividers && isOpaque() && !super.isOpaque();
 
             if (fillForMissingDividers && mDividerPaint == null && mIsCacheColorOpaque) {
                 mDividerPaint = new Paint();
@@ -2965,15 +3018,22 @@
             }
             final Paint paint = mDividerPaint;
 
+            final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
             if (!mStackFromBottom) {
-                int bottom;
-                int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
+                int bottom = 0;
                 
-                // Draw top divider for overscroll
-                if (count > 0 && mScrollY < 0) {
-                    bounds.bottom = 0;
-                    bounds.top = -dividerHeight;
-                    drawDivider(canvas, bounds, -1);
+                // Draw top divider or header for overscroll
+                final int scrollY = mScrollY;
+                if (count > 0 && scrollY < 0) {
+                    if (drawOverscrollHeader) {
+                        bounds.bottom = 0;
+                        bounds.top = scrollY;
+                        drawOverscrollHeader(canvas, overscrollHeader, bounds);
+                    } else if (drawDividers) {
+                        bounds.bottom = 0;
+                        bounds.top = -dividerHeight;
+                        drawDivider(canvas, bounds, -1);
+                    }
                 }
 
                 for (int i = 0; i < count; i++) {
@@ -2982,7 +3042,8 @@
                         View child = getChildAt(i);
                         bottom = child.getBottom();
                         // Don't draw dividers next to items that are not enabled
-                        if (bottom < listBottom) {
+                        if (drawDividers &&
+                                (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
                             if ((areAllItemsSelectable ||
                                     (adapter.isEnabled(first + i) && (i == count - 1 ||
                                             adapter.isEnabled(first + i + 1))))) {
@@ -2997,17 +3058,34 @@
                         }
                     }
                 }
+                
+                final int overFooterBottom = mBottom + mScrollY;
+                if (drawOverscrollFooter && first + count == itemCount &&
+                        overFooterBottom > bottom) {
+                    bounds.top = bottom;
+                    bounds.bottom = overFooterBottom;
+                    drawOverscrollFooter(canvas, overscrollFooter, bounds);
+                }
             } else {
                 int top;
                 int listTop = mListPadding.top;
 
-                for (int i = 0; i < count; i++) {
+                final int scrollY = mScrollY;
+                
+                if (count > 0 && drawOverscrollHeader) {
+                    bounds.top = scrollY;
+                    bounds.bottom = getChildAt(0).getTop();
+                    drawOverscrollHeader(canvas, overscrollHeader, bounds);
+                }
+
+                final int start = drawOverscrollHeader ? 1 : 0;
+                for (int i = start; i < count; i++) {
                     if ((headerDividers || first + i >= headerCount) &&
                             (footerDividers || first + i < footerLimit)) {
                         View child = getChildAt(i);
                         top = child.getTop();
                         // Don't draw dividers next to items that are not enabled
-                        if (top > listTop) {
+                        if (drawDividers && top > listTop) {
                             if ((areAllItemsSelectable ||
                                     (adapter.isEnabled(first + i) && (i == count - 1 ||
                                             adapter.isEnabled(first + i + 1))))) {
@@ -3026,6 +3104,19 @@
                         }
                     }
                 }
+                
+                if (count > 0 && scrollY > 0) {
+                    if (drawOverscrollFooter) {
+                        final int absListBottom = mBottom;
+                        bounds.top = absListBottom;
+                        bounds.bottom = absListBottom + scrollY;
+                        drawOverscrollFooter(canvas, overscrollFooter, bounds);
+                    } else if (drawDividers) {
+                        bounds.top = listBottom;
+                        bounds.bottom = listBottom + dividerHeight;
+                        drawDivider(canvas, bounds, -1);
+                    }
+                }
             }
         }
 
@@ -3132,6 +3223,45 @@
         mFooterDividersEnabled = footerDividersEnabled;
         invalidate();
     }
+    
+    /**
+     * Sets the drawable that will be drawn above all other list content.
+     * This area can become visible when the user overscrolls the list.
+     * 
+     * @param header The drawable to use
+     */
+    public void setOverscrollHeader(Drawable header) {
+        mOverscrollHeader = header;
+        if (mScrollY < 0) {
+            invalidate();
+        }
+    }
+    
+    /**
+     * @return The drawable that will be drawn above all other list content
+     */
+    public Drawable getOverscrollHeader() {
+        return mOverscrollHeader;
+    }
+    
+    /**
+     * Sets the drawable that will be drawn below all other list content.
+     * This area can become visible when the user overscrolls the list,
+     * or when the list's content does not fully fill the container area.
+     * 
+     * @param footer The drawable to use
+     */
+    public void setOverscrollFooter(Drawable footer) {
+        mOverscrollFooter = footer;
+        invalidate();
+    }
+    
+    /**
+     * @return The drawable that will be drawn below all other list content
+     */
+    public Drawable getOverscrollFooter() {
+        return mOverscrollFooter;
+    }
 
     @Override
     protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 1ced121..f5e2f1d 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -1747,6 +1747,10 @@
         <!-- When set to false, the ListView will not draw the divider before each footer view.
              The default value is true. -->
         <attr name="footerDividersEnabled" format="boolean" />
+        <!-- Drawable to draw above list content. -->
+        <attr name="overscrollHeader" format="reference" />
+        <!-- Drawable to draw below list content. -->
+        <attr name="overscrollFooter" format="reference" />
     </declare-styleable>
     <declare-styleable name="MenuView">
         <!-- Default appearance of menu item text. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 36a8f5b..8c00884 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1235,6 +1235,8 @@
   <public type="attr" name="stripLeft" id="0x010102bc" />
   <public type="attr" name="stripRight" id="0x010102bd" />
   <public type="attr" name="stripEnabled" id="0x010102be" />
+  <public type="attr" name="overscrollHeader" id="0x010102bf" />
+  <public type="attr" name="overscrollFooter" id="0x010102c0" />
 
   <public type="anim" name="cycle_interpolator" id="0x010a000c" />