Merge "Don't drop zeros in the second position in formatDuration()"
diff --git a/api/current.xml b/api/current.xml
index 403e3d0..7624086 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -6774,6 +6774,39 @@
  visibility="public"
 >
 </field>
+<field name="overScrollFooter"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843459"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="overScrollHeader"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843458"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="overScrollMode"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843457"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="padding"
  type="int"
  transient="false"
@@ -18961,7 +18994,7 @@
  visibility="public"
 >
 <method name="after"
- return="void"
+ return="android.animation.AnimatorSet.Builder"
  abstract="false"
  native="false"
  synchronized="false"
@@ -18974,7 +19007,7 @@
 </parameter>
 </method>
 <method name="after"
- return="void"
+ return="android.animation.AnimatorSet.Builder"
  abstract="false"
  native="false"
  synchronized="false"
@@ -18987,7 +19020,7 @@
 </parameter>
 </method>
 <method name="before"
- return="void"
+ return="android.animation.AnimatorSet.Builder"
  abstract="false"
  native="false"
  synchronized="false"
@@ -19000,7 +19033,7 @@
 </parameter>
 </method>
 <method name="with"
- return="void"
+ return="android.animation.AnimatorSet.Builder"
  abstract="false"
  native="false"
  synchronized="false"
@@ -157973,6 +158006,17 @@
  visibility="public"
 >
 </field>
+<field name="ACTION_MTP_SESSION_END"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.provider.action.MTP_SESSION_END&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="ACTION_VIDEO_CAPTURE"
  type="java.lang.String"
  transient="false"
@@ -205682,6 +205726,17 @@
  visibility="public"
 >
 </method>
+<method name="getOverScrollMode"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getPaddingBottom"
  return="int"
  abstract="false"
@@ -207013,6 +207068,25 @@
 <parameter name="heightMeasureSpec" type="int">
 </parameter>
 </method>
+<method name="onOverScrolled"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="scrollX" type="int">
+</parameter>
+<parameter name="scrollY" type="int">
+</parameter>
+<parameter name="clampedX" type="boolean">
+</parameter>
+<parameter name="clampedY" type="boolean">
+</parameter>
+</method>
 <method name="onRestoreInstanceState"
  return="void"
  abstract="false"
@@ -207166,6 +207240,35 @@
 <parameter name="visibility" type="int">
 </parameter>
 </method>
+<method name="overScrollBy"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="deltaX" type="int">
+</parameter>
+<parameter name="deltaY" type="int">
+</parameter>
+<parameter name="scrollX" type="int">
+</parameter>
+<parameter name="scrollY" type="int">
+</parameter>
+<parameter name="scrollRangeX" type="int">
+</parameter>
+<parameter name="scrollRangeY" type="int">
+</parameter>
+<parameter name="maxOverScrollX" type="int">
+</parameter>
+<parameter name="maxOverScrollY" type="int">
+</parameter>
+<parameter name="isTouchEvent" type="boolean">
+</parameter>
+</method>
 <method name="performClick"
  return="boolean"
  abstract="false"
@@ -208083,6 +208186,19 @@
 <parameter name="l" type="android.view.View.OnTouchListener">
 </parameter>
 </method>
+<method name="setOverScrollMode"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="overScrollMode" type="int">
+</parameter>
+</method>
 <method name="setPadding"
  return="void"
  abstract="false"
@@ -208917,6 +209033,39 @@
  visibility="public"
 >
 </field>
+<field name="OVER_SCROLL_ALWAYS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="0"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OVER_SCROLL_IF_CONTENT_SCROLLS"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="OVER_SCROLL_NEVER"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="PRESSED_ENABLED_FOCUSED_SELECTED_STATE_SET"
  type="int[]"
  transient="false"
@@ -209797,6 +209946,28 @@
  visibility="public"
 >
 </method>
+<method name="getScaledOverflingDistance"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getScaledOverscrollDistance"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getScaledPagingTouchSlop"
  return="int"
  abstract="false"
@@ -221035,7 +221206,7 @@
 >
 </method>
 <method name="getShortcutInputMethodsAndSubtypes"
- return="java.util.List&lt;android.util.Pair&lt;android.view.inputmethod.InputMethodInfo, android.view.inputmethod.InputMethodSubtype&gt;&gt;"
+ return="java.util.Map&lt;android.view.inputmethod.InputMethodInfo, java.util.List&lt;android.view.inputmethod.InputMethodSubtype&gt;&gt;"
  abstract="false"
  native="false"
  synchronized="false"
@@ -221222,6 +221393,23 @@
 <parameter name="id" type="java.lang.String">
 </parameter>
 </method>
+<method name="setInputMethodAndSubtype"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="token" type="android.os.IBinder">
+</parameter>
+<parameter name="id" type="java.lang.String">
+</parameter>
+<parameter name="subtype" type="android.view.inputmethod.InputMethodSubtype">
+</parameter>
+</method>
 <method name="showInputMethodAndSubtypeEnabler"
  return="void"
  abstract="false"
@@ -224647,6 +224835,17 @@
  visibility="public"
 >
 </method>
+<method name="getUseWebViewBackgroundForOverscrollBackground"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getUseWideViewPort"
  return="boolean"
  abstract="false"
@@ -225252,6 +225451,19 @@
 <parameter name="use" type="boolean">
 </parameter>
 </method>
+<method name="setUseWebViewBackgroundForOverscrollBackground"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="view" type="boolean">
+</parameter>
+</method>
 <method name="setUseWideViewPort"
  return="void"
  abstract="false"
@@ -237693,6 +237905,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="removeFooterView"
  return="boolean"
  abstract="false"
@@ -237784,6 +238018,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"
@@ -238335,6 +238595,334 @@
 </parameter>
 </method>
 </interface>
+<class name="OverScroller"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="OverScroller"
+ type="android.widget.OverScroller"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+</constructor>
+<constructor name="OverScroller"
+ type="android.widget.OverScroller"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="context" type="android.content.Context">
+</parameter>
+<parameter name="interpolator" type="android.graphics.Interpolator">
+</parameter>
+<parameter name="bounceCoefficientX" type="float">
+</parameter>
+<parameter name="bounceCoefficientY" type="float">
+</parameter>
+<parameter name="flywheel" type="boolean">
+</parameter>
+</constructor>
+<method name="abortAnimation"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="computeScrollOffset"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="fling"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startX" type="int">
+</parameter>
+<parameter name="startY" type="int">
+</parameter>
+<parameter name="velocityX" type="int">
+</parameter>
+<parameter name="velocityY" type="int">
+</parameter>
+<parameter name="minX" type="int">
+</parameter>
+<parameter name="maxX" type="int">
+</parameter>
+<parameter name="minY" type="int">
+</parameter>
+<parameter name="maxY" type="int">
+</parameter>
+</method>
+<method name="fling"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startX" type="int">
+</parameter>
+<parameter name="startY" type="int">
+</parameter>
+<parameter name="velocityX" type="int">
+</parameter>
+<parameter name="velocityY" type="int">
+</parameter>
+<parameter name="minX" type="int">
+</parameter>
+<parameter name="maxX" type="int">
+</parameter>
+<parameter name="minY" type="int">
+</parameter>
+<parameter name="maxY" type="int">
+</parameter>
+<parameter name="overX" type="int">
+</parameter>
+<parameter name="overY" type="int">
+</parameter>
+</method>
+<method name="forceFinished"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="finished" type="boolean">
+</parameter>
+</method>
+<method name="getCurrX"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getCurrY"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getFinalX"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getFinalY"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getStartX"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getStartY"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isFinished"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="isOverScrolled"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="notifyHorizontalEdgeReached"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startX" type="int">
+</parameter>
+<parameter name="finalX" type="int">
+</parameter>
+<parameter name="overX" type="int">
+</parameter>
+</method>
+<method name="notifyVerticalEdgeReached"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startY" type="int">
+</parameter>
+<parameter name="finalY" type="int">
+</parameter>
+<parameter name="overY" type="int">
+</parameter>
+</method>
+<method name="setFriction"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="friction" type="float">
+</parameter>
+</method>
+<method name="springBack"
+ return="boolean"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startX" type="int">
+</parameter>
+<parameter name="startY" type="int">
+</parameter>
+<parameter name="minX" type="int">
+</parameter>
+<parameter name="maxX" type="int">
+</parameter>
+<parameter name="minY" type="int">
+</parameter>
+<parameter name="maxY" type="int">
+</parameter>
+</method>
+<method name="startScroll"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startX" type="int">
+</parameter>
+<parameter name="startY" type="int">
+</parameter>
+<parameter name="dx" type="int">
+</parameter>
+<parameter name="dy" type="int">
+</parameter>
+</method>
+<method name="startScroll"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="startX" type="int">
+</parameter>
+<parameter name="startY" type="int">
+</parameter>
+<parameter name="dx" type="int">
+</parameter>
+<parameter name="dy" type="int">
+</parameter>
+<parameter name="duration" type="int">
+</parameter>
+</method>
+</class>
 <class name="PopupMenu"
  extends="java.lang.Object"
  abstract="false"
@@ -248624,7 +249212,7 @@
  deprecated="not deprecated"
  visibility="public"
 >
-<parameter name="t" type="T">
+<parameter name="arg0" type="T">
 </parameter>
 </method>
 </interface>
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 9ba9388..f5420d1 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -341,6 +341,20 @@
         return this;
     }
 
+    @Override
+    public void setupStartValues() {
+        for (Node node : mNodes) {
+            node.animation.setupStartValues();
+        }
+    }
+
+    @Override
+    public void setupEndValues() {
+        for (Node node : mNodes) {
+            node.animation.setupEndValues();
+        }
+    }
+
     /**
      * {@inheritDoc}
      *
@@ -401,6 +415,7 @@
                     }
                 }
             });
+            delayAnim.start();
         }
         if (mListeners != null) {
             ArrayList<AnimatorListener> tmpListeners =
@@ -408,6 +423,11 @@
             int numListeners = tmpListeners.size();
             for (int i = 0; i < numListeners; ++i) {
                 tmpListeners.get(i).onAnimationStart(this);
+                if (mNodes.size() == 0) {
+                    // Handle unusual case where empty AnimatorSet is started - should send out
+                    // end event immediately since the event will not be sent out at all otherwise
+                    tmpListeners.get(i).onAnimationEnd(this);
+                }
             }
         }
     }
@@ -894,7 +914,7 @@
          * @param anim The animation that will play when the animation supplied to the
          * {@link AnimatorSet#play(Animator)} method starts.
          */
-        public void with(Animator anim) {
+        public Builder with(Animator anim) {
             Node node = mNodeMap.get(anim);
             if (node == null) {
                 node = new Node(anim);
@@ -903,6 +923,7 @@
             }
             Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
             node.addDependency(dependency);
+            return this;
         }
 
         /**
@@ -913,7 +934,7 @@
          * @param anim The animation that will play when the animation supplied to the
          * {@link AnimatorSet#play(Animator)} method ends.
          */
-        public void before(Animator anim) {
+        public Builder before(Animator anim) {
             Node node = mNodeMap.get(anim);
             if (node == null) {
                 node = new Node(anim);
@@ -922,6 +943,7 @@
             }
             Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
             node.addDependency(dependency);
+            return this;
         }
 
         /**
@@ -932,7 +954,7 @@
          * @param anim The animation whose end will cause the animation supplied to the
          * {@link AnimatorSet#play(Animator)} method to play.
          */
-        public void after(Animator anim) {
+        public Builder after(Animator anim) {
             Node node = mNodeMap.get(anim);
             if (node == null) {
                 node = new Node(anim);
@@ -941,6 +963,7 @@
             }
             Dependency dependency = new Dependency(node, Dependency.AFTER);
             mCurrentNode.addDependency(dependency);
+            return this;
         }
 
         /**
@@ -951,11 +974,12 @@
          * @param delay The number of milliseconds that should elapse before the
          * animation starts.
          */
-        public void after(long delay) {
+        public Builder after(long delay) {
             // setup dummy ValueAnimator just to run the clock
             ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
             anim.setDuration(delay);
             after(anim);
+            return this;
         }
 
     }
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index b021e75..e192067 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -19,6 +19,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.AndroidRuntimeException;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AnimationUtils;
 
@@ -860,21 +861,22 @@
     /**
      * Start the animation playing. This version of start() takes a boolean flag that indicates
      * whether the animation should play in reverse. The flag is usually false, but may be set
-     * to true if called from the reverse() method/
+     * to true if called from the reverse() method.
+     *
+     * <p>The animation started by calling this method will be run on the thread that called
+     * this method. This thread should have a Looper on it (a runtime exception will be thrown if
+     * this is not the case). Also, if the animation will animate
+     * properties of objects in the view hierarchy, then the calling thread should be the UI
+     * thread for that view hierarchy.</p>
      *
      * @param playBackwards Whether the ValueAnimator should start playing in reverse.
      */
     private void start(boolean playBackwards) {
-        mPlayingBackwards = playBackwards;
-        Looper looper = Looper.getMainLooper();
-        final boolean isUiThread;
-        if (looper != null) {
-            isUiThread = Thread.currentThread() == looper.getThread();
-        } else {
-            // ignore check if we don't have a Looper (this isn't an Activity)
-            isUiThread = true;
+        if (Looper.myLooper() == null) {
+            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
         }
-        if ((mStartDelay == 0) && isUiThread) {
+        mPlayingBackwards = playBackwards;
+        if (mStartDelay == 0) {
             if (mListeners != null) {
                 ArrayList<AnimatorListener> tmpListeners =
                         (ArrayList<AnimatorListener>) mListeners.clone();
@@ -912,9 +914,14 @@
                 listener.onAnimationCancel(this);
             }
         }
-        // Just set the CANCELED flag - this causes the animation to end the next time a frame
-        // is processed.
-        mPlayingState = CANCELED;
+        // Only cancel if the animation is actually running or has been started and is about
+        // to run
+        if (mPlayingState != STOPPED || sPendingAnimations.get().contains(this) ||
+                sDelayedAnims.get().contains(this)) {
+            // Just set the CANCELED flag - this causes the animation to end the next time a frame
+            // is processed.
+            mPlayingState = CANCELED;
+        }
     }
 
     @Override
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 273e3c6..f3cc4ee 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -41,6 +41,7 @@
 import android.text.TextUtils;
 import android.util.Config;
 import android.util.Log;
+import android.util.Singleton;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -52,8 +53,7 @@
      * Cast a Binder object into an activity manager interface, generating
      * a proxy if needed.
      */
-    static public IActivityManager asInterface(IBinder obj)
-    {
+    static public IActivityManager asInterface(IBinder obj) {
         if (obj == null) {
             return null;
         }
@@ -62,27 +62,15 @@
         if (in != null) {
             return in;
         }
-        
+
         return new ActivityManagerProxy(obj);
     }
-    
+
     /**
      * Retrieve the system's default/global activity manager.
      */
-    static public IActivityManager getDefault()
-    {
-        if (gDefault != null) {
-            //if (Config.LOGV) Log.v(
-            //    "ActivityManager", "returning cur default = " + gDefault);
-            return gDefault;
-        }
-        IBinder b = ServiceManager.getService("activity");
-        if (Config.LOGV) Log.v(
-            "ActivityManager", "default service binder = " + b);
-        gDefault = asInterface(b);
-        if (Config.LOGV) Log.v(
-            "ActivityManager", "default service = " + gDefault);
-        return gDefault;
+    static public IActivityManager getDefault() {
+        return gDefault.get();
     }
 
     /**
@@ -95,13 +83,12 @@
         return sSystemReady;
     }
     static boolean sSystemReady = false;
-    
+
     /**
      * Convenience for sending a sticky broadcast.  For internal use only.
      * If you don't care about permission, use null.
      */
-    static public void broadcastStickyIntent(Intent intent, String permission)
-    {
+    static public void broadcastStickyIntent(Intent intent, String permission) {
         try {
             getDefault().broadcastIntent(
                 null, intent, null, null, Activity.RESULT_OK, null, null,
@@ -117,8 +104,7 @@
         }
     }
 
-    public ActivityManagerNative()
-    {
+    public ActivityManagerNative() {
         attachInterface(this, descriptor);
     }
     
@@ -1390,16 +1376,27 @@
         }
 
         }
-        
+
         return super.onTransact(code, data, reply, flags);
     }
 
-    public IBinder asBinder()
-    {
+    public IBinder asBinder() {
         return this;
     }
 
-    private static IActivityManager gDefault;
+    private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
+        protected IActivityManager create() {
+            IBinder b = ServiceManager.getService("activity");
+            if (Config.LOGV) {
+                Log.v("ActivityManager", "default service binder = " + b);
+            }
+            IActivityManager am = asInterface(b);
+            if (Config.LOGV) {
+                Log.v("ActivityManager", "default service = " + am);
+            }
+            return am;
+        }
+    };
 }
 
 class ActivityManagerProxy implements IActivityManager
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c0714e3..5c4f57a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -870,6 +870,12 @@
                             (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
                             (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
                             dbStats.cache, dbStats.dbName);
+                    if (dbStats.dataDump != null) {
+                        int size = dbStats.dataDump.size();
+                        for (int dumpIndex = 0; dumpIndex < size; dumpIndex++) {
+                            printRow(pw, "%s", dbStats.dataDump.get(dumpIndex));
+                        }
+                    }
                 }
             }
 
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 09a21f8..4aa4d77 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -841,6 +841,12 @@
         }
         ContentValues values = new ContentValues();
         values.put(Downloads.Impl.COLUMN_DELETED, 1);
+        // if only one id is passed in, then include it in the uri itself.
+        // this will eliminate a full database scan in the download service.
+        if (ids.length == 1) {
+            return mResolver.update(ContentUris.withAppendedId(mBaseUri, ids[0]), values,
+                    null, null);
+        } 
         return mResolver.update(mBaseUri, values, getWhereClauseForIds(ids),
                 getWhereArgsForIds(ids));
     }
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 7efb7fd..87f55d2 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2507,7 +2507,7 @@
                     if (pageCount > 0) {
                         dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
                                 lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(),
-                                db.getCachesize()));
+                                db.getCachesize(), getDataDump(db)));
                     }
                 }
                 // if there are pooled connections, return the cache stats for them also.
@@ -2518,7 +2518,7 @@
                     for (SQLiteDatabase pDb : connPool.getConnectionList()) {
                         dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
                                 + lastnode, 0, 0, 0, pDb.getCacheHitNum(),
-                                pDb.getCacheMissNum(), pDb.getCachesize()));
+                                pDb.getCacheMissNum(), pDb.getCachesize(), null));
                     }
                 }
             } catch (SQLiteException e) {
@@ -2529,6 +2529,44 @@
         return dbStatsList;
     }
 
+    private static ArrayList<String> getDataDump(SQLiteDatabase db) {
+        // create database dump of certain data from certain databases for debugging purposes
+        if (db.getPath().equalsIgnoreCase(
+                "/data/data/com.android.providers.downloads/databases/downloads.db")) {
+            String sql =
+                    "select * from downloads " +
+                    " where notificationpackage = 'com.google.android.gsf'" + 
+                    " or status >= 400";
+            Cursor cursor = db.rawQuery(sql, null);
+            try {
+                int count = cursor.getCount();
+                if (count == 0) {
+                    return null;
+                }
+                ArrayList<String> buff = new ArrayList<String>();
+                buff.add("  Data from downloads.db");
+                int columnCount = cursor.getColumnCount();
+                for (int i =0; i < count && cursor.moveToNext(); i++) {
+                    buff.add("    Row#" + i + "");
+                    for (int j = 0; j < columnCount; j++) {
+                        String colName = cursor.getColumnName(j);
+                        String value = cursor.getString(j);
+                        buff.add("      " + colName + " = " + value);
+                    }
+                }
+                for (String s : buff)  Log.i("vnoritag", s);
+                return buff;
+            } catch (SQLiteException e) {
+                Log.w(TAG, "exception in executing the sql: " + sql, e);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+        return null;
+    }
+
     /**
      * Returns list of full pathnames of all attached databases including the main database
      * by executing 'pragma database_list' on the database.
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 94960791..72377f0 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -121,27 +121,31 @@
      */
     public static class DbStats {
         /** name of the database */
-        public String dbName;
+        public final String dbName;
 
         /** the page size for the database */
-        public long pageSize;
+        public final long pageSize;
 
         /** the database size */
-        public long dbSize;
+        public final long dbSize;
 
         /** documented here http://www.sqlite.org/c3ref/c_dbstatus_lookaside_used.html */
-        public int lookaside;
+        public final int lookaside;
 
         /** statement cache stats: hits/misses/cachesize */
-        public String cache;
+        public final String cache;
+
+        /** database dump of 'useful info for debugging only */
+        public final ArrayList<String> dataDump;
 
         public DbStats(String dbName, long pageCount, long pageSize, int lookaside,
-            int hits, int misses, int cachesize) {
+            int hits, int misses, int cachesize, ArrayList<String> data) {
             this.dbName = dbName;
             this.pageSize = pageSize / 1024;
             dbSize = (pageCount * pageSize) / 1024;
             this.lookaside = lookaside;
             this.cache = hits + "/" + misses + "/" + cachesize;
+            this.dataDump = data;
         }
     }
 
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 2b4f39a..1c295a7 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -513,21 +513,27 @@
     }
 
     /**
-     * Count the number and aggregate size of memory allocations between
-     * two points.
+     * Start counting the number and aggregate size of memory allocations.
      *
-     * The "start" function resets the counts and enables counting.  The
-     * "stop" function disables the counting so that the analysis code
-     * doesn't cause additional allocations.  The "get" function returns
-     * the specified value.
+     * <p>The {@link #startAllocCounting() start} function resets the counts and enables counting.
+     * The {@link #stopAllocCounting() stop} function disables the counting so that the analysis
+     * code doesn't cause additional allocations.  The various <code>get</code> functions return
+     * the specified value. And the various <code>reset</code> functions reset the specified
+     * count.</p>
      *
-     * Counts are kept for the system as a whole and for each thread.
+     * <p>Counts are kept for the system as a whole and for each thread.
      * The per-thread counts for threads other than the current thread
-     * are not cleared by the "reset" or "start" calls.
+     * are not cleared by the "reset" or "start" calls.</p>
      */
     public static void startAllocCounting() {
         VMDebug.startAllocCounting();
     }
+
+    /**
+     * Stop counting the number and aggregate size of memory allocations.
+     *
+     * @see #startAllocCounting()
+     */
     public static void stopAllocCounting() {
         VMDebug.stopAllocCounting();
     }
@@ -671,11 +677,11 @@
      * for catching regressions in code that is expected to operate
      * without causing any allocations.
      *
-     * Pass in the maximum number of allowed allocations.  Use -1 to disable
-     * the limit.  Returns the previous limit.
+     * <p>Pass in the maximum number of allowed allocations.  Use -1 to disable
+     * the limit.  Returns the previous limit.</p>
      *
-     * The preferred way to use this is:
-     *
+     * <p>The preferred way to use this is:
+     * <pre>
      *  int prevLimit = -1;
      *  try {
      *      prevLimit = Debug.setAllocationLimit(0);
@@ -683,16 +689,16 @@
      *  } finally {
      *      Debug.setAllocationLimit(prevLimit);
      *  }
-     *
+     * </pre>
      * This allows limits to be nested.  The try/finally ensures that the
-     * limit is reset if something fails.
+     * limit is reset if something fails.</p>
      *
-     * Exceeding the limit causes a dalvik.system.AllocationLimitError to
+     * <p>Exceeding the limit causes a dalvik.system.AllocationLimitError to
      * be thrown from a memory allocation call.  The limit is reset to -1
-     * when this happens.
+     * when this happens.</p>
      *
-     * The feature may be disabled in the VM configuration.  If so, this
-     * call has no effect, and always returns -1.
+     * <p>The feature may be disabled in the VM configuration.  If so, this
+     * call has no effect, and always returns -1.</p>
      */
     public static int setAllocationLimit(int limit) {
         return VMDebug.setAllocationLimit(limit);
@@ -846,6 +852,7 @@
      * API for gathering and querying instruction counts.
      *
      * Example usage:
+     * <pre>
      *   Debug.InstructionCount icount = new Debug.InstructionCount();
      *   icount.resetAndStart();
      *    [... do lots of stuff ...]
@@ -855,6 +862,7 @@
      *       System.out.println("Method invocations: "
      *           + icount.globalMethodInvocations());
      *   }
+     * </pre>
      */
     public static class InstructionCount {
         private static final int NUM_INSTR = 256;
diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java
index d360140..898c642 100644
--- a/core/java/android/os/Looper.java
+++ b/core/java/android/os/Looper.java
@@ -17,7 +17,9 @@
 package android.os;
 
 import android.util.Config;
+import android.util.Log;
 import android.util.Printer;
+import android.util.PrefixPrinter;
 
 /**
   * Class used to run a message loop for a thread.  Threads by default do
@@ -31,37 +33,38 @@
   * <p>This is a typical example of the implementation of a Looper thread,
   * using the separation of {@link #prepare} and {@link #loop} to create an
   * initial Handler to communicate with the Looper.
-  * 
+  *
   * <pre>
   *  class LooperThread extends Thread {
   *      public Handler mHandler;
-  *      
+  *
   *      public void run() {
   *          Looper.prepare();
-  *          
+  *
   *          mHandler = new Handler() {
   *              public void handleMessage(Message msg) {
   *                  // process incoming messages here
   *              }
   *          };
-  *          
+  *
   *          Looper.loop();
   *      }
   *  }</pre>
   */
 public class Looper {
-    private static final boolean DEBUG = false;
-    private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    private static final String TAG = "Looper";
+    private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE);
 
     // sThreadLocal.get() will return null unless you've called prepare().
-    private static final ThreadLocal sThreadLocal = new ThreadLocal();
+    private static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
 
     final MessageQueue mQueue;
+    final Thread mThread;
     volatile boolean mRun;
-    Thread mThread;
+
     private Printer mLogging = null;
-    private static Looper mMainLooper = null;
-    
+    private static Looper mMainLooper = null;  // guarded by Looper.class
+
      /** Initialize the current thread as a looper.
       * This gives you a chance to create handlers that then reference
       * this looper, before actually starting the loop. Be sure to call
@@ -74,13 +77,13 @@
         }
         sThreadLocal.set(new Looper());
     }
-    
-    /** Initialize the current thread as a looper, marking it as an application's main 
-     *  looper. The main looper for your application is created by the Android environment,
-     *  so you should never need to call this function yourself.
-     * {@link #prepare()}
+
+    /**
+     * Initialize the current thread as a looper, marking it as an
+     * application's main looper. The main looper for your application
+     * is created by the Android environment, so you should never need
+     * to call this function yourself.  See also: {@link #prepare()}
      */
-     
     public static final void prepareMainLooper() {
         prepare();
         setMainLooper(myLooper());
@@ -92,7 +95,7 @@
     private synchronized static void setMainLooper(Looper looper) {
         mMainLooper = looper;
     }
-    
+
     /** Returns the application's main looper, which lives in the main thread of the application.
      */
     public synchronized static final Looper getMainLooper() {
@@ -100,28 +103,28 @@
     }
 
     /**
-     *  Run the message queue in this thread. Be sure to call
+     * Run the message queue in this thread. Be sure to call
      * {@link #quit()} to end the loop.
      */
     public static final void loop() {
         Looper me = myLooper();
+        if (me == null) {
+            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
+        }
         MessageQueue queue = me.mQueue;
         while (true) {
             Message msg = queue.next(); // might block
-            //if (!me.mRun) {
-            //    break;
-            //}
             if (msg != null) {
                 if (msg.target == null) {
                     // No target is a magic identifier for the quit message.
                     return;
                 }
-                if (me.mLogging!= null) me.mLogging.println(
+                if (me.mLogging != null) me.mLogging.println(
                         ">>>>> Dispatching to " + msg.target + " "
                         + msg.callback + ": " + msg.what
                         );
                 msg.target.dispatchMessage(msg);
-                if (me.mLogging!= null) me.mLogging.println(
+                if (me.mLogging != null) me.mLogging.println(
                         "<<<<< Finished to    " + msg.target + " "
                         + msg.callback);
                 msg.recycle();
@@ -134,7 +137,7 @@
      * null if the calling thread is not associated with a Looper.
      */
     public static final Looper myLooper() {
-        return (Looper)sThreadLocal.get();
+        return sThreadLocal.get();
     }
 
     /**
@@ -179,28 +182,29 @@
     public Thread getThread() {
         return mThread;
     }
-    
+
     /** @hide */
     public MessageQueue getQueue() {
         return mQueue;
     }
-    
+
     public void dump(Printer pw, String prefix) {
-        pw.println(prefix + this);
-        pw.println(prefix + "mRun=" + mRun);
-        pw.println(prefix + "mThread=" + mThread);
-        pw.println(prefix + "mQueue=" + ((mQueue != null) ? mQueue : "(null"));
+        pw = PrefixPrinter.create(pw, prefix);
+        pw.println(this.toString());
+        pw.println("mRun=" + mRun);
+        pw.println("mThread=" + mThread);
+        pw.println("mQueue=" + ((mQueue != null) ? mQueue : "(null"));
         if (mQueue != null) {
             synchronized (mQueue) {
                 long now = SystemClock.uptimeMillis();
                 Message msg = mQueue.mMessages;
                 int n = 0;
                 while (msg != null) {
-                    pw.println(prefix + "  Message " + n + ": " + msg.toString(now));
+                    pw.println("  Message " + n + ": " + msg.toString(now));
                     n++;
                     msg = msg.next;
                 }
-                pw.println(prefix + "(Total messages: " + n + ")");
+                pw.println("(Total messages: " + n + ")");
             }
         }
     }
@@ -226,4 +230,3 @@
         }
     }
 }
-
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 86322ac..854428f 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -19,9 +19,11 @@
 import android.app.ActivityManagerNative;
 import android.app.ActivityThread;
 import android.app.ApplicationErrorReport;
+import android.app.IActivityManager;
 import android.content.Intent;
 import android.util.Log;
 import android.util.Printer;
+import android.util.Singleton;
 import android.view.IWindowManager;
 
 import com.android.internal.os.RuntimeInit;
@@ -922,10 +924,8 @@
                 return;
             }
 
-            // TODO: cache the window manager stub?
             final IWindowManager windowManager = (info.policy & PENALTY_FLASH) != 0 ?
-                    IWindowManager.Stub.asInterface(ServiceManager.getService("window")) :
-                    null;
+                    sWindowManager.get() : null;
             if (windowManager != null) {
                 try {
                     windowManager.showStrictModeViolation(true);
@@ -988,7 +988,7 @@
             }
 
             // Not perfect, but fast and good enough for dup suppression.
-            Integer crashFingerprint = info.crashInfo.stackTrace.hashCode();
+            Integer crashFingerprint = info.hashCode();
             long lastViolationTime = 0;
             if (mLastViolationTime.containsKey(crashFingerprint)) {
                 lastViolationTime = mLastViolationTime.get(crashFingerprint);
@@ -1092,11 +1092,15 @@
             public void run() {
                 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                 try {
-                    ActivityManagerNative.getDefault().
-                            handleApplicationStrictModeViolation(
-                                RuntimeInit.getApplicationObject(),
-                                violationMaskSubset,
-                                info);
+                    IActivityManager am = ActivityManagerNative.getDefault();
+                    if (am == null) {
+                        Log.d(TAG, "No activity manager; failed to Dropbox violation.");
+                    } else {
+                        am.handleApplicationStrictModeViolation(
+                            RuntimeInit.getApplicationObject(),
+                            violationMaskSubset,
+                            info);
+                    }
                 } catch (RemoteException e) {
                     Log.e(TAG, "RemoteException handling StrictMode violation", e);
                 }
@@ -1404,6 +1408,12 @@
         }
     };
 
+    private static Singleton<IWindowManager> sWindowManager = new Singleton<IWindowManager>() {
+        protected IWindowManager create() {
+            return IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
+        }
+    };
+
     /**
      * Enter a named critical span (e.g. an animation)
      *
@@ -1545,6 +1555,24 @@
             }
         }
 
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result = 37 * result + crashInfo.stackTrace.hashCode();
+            if (numAnimationsRunning != 0) {
+                result *= 37;
+            }
+            if (broadcastIntentAction != null) {
+                result = 37 * result + broadcastIntentAction.hashCode();
+            }
+            if (tags != null) {
+                for (String tag : tags) {
+                    result = 37 * result + tag.hashCode();
+                }
+            }
+            return result;
+        }
+
         /**
          * Create an instance of ViolationInfo initialized from a Parcel.
          */
diff --git a/core/java/android/preference/PreferenceFrameLayout.java b/core/java/android/preference/PreferenceFrameLayout.java
new file mode 100644
index 0000000..426abf0
--- /dev/null
+++ b/core/java/android/preference/PreferenceFrameLayout.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.preference;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+
+/**
+ * @hide
+ */
+public class PreferenceFrameLayout extends FrameLayout {
+    private static final int DEFAULT_TOP_PADDING = 0;
+    private static final int DEFAULT_BOTTOM_PADDING = 0;
+    private final int mTopPadding;
+    private final int mBottomPadding;
+    private boolean mPaddingApplied = false;
+
+    public PreferenceFrameLayout(Context context) {
+        this(context, null);
+    }
+
+    public PreferenceFrameLayout(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.preferenceFrameLayoutStyle);
+    }
+
+    public PreferenceFrameLayout(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.PreferenceFrameLayout, defStyle, 0);
+
+        mTopPadding = (int) a.getDimension(
+                com.android.internal.R.styleable.PreferenceFrameLayout_topPadding,
+                DEFAULT_TOP_PADDING);
+        mBottomPadding = (int) a.getDimension(
+                com.android.internal.R.styleable.PreferenceFrameLayout_bottomPadding,
+                DEFAULT_BOTTOM_PADDING);
+
+        a.recycle();
+    }
+
+    @Override
+    public void addView(View child) {
+        int topPadding = getPaddingTop();
+        int bottomPadding = getPaddingBottom();
+        // Check on the id of the child before adding it.
+        if (child != null && child.getId() != com.android.internal.R.id.default_preference_layout) {
+            // Add the padding to the view group after determining if the padding already exists.
+            if (!mPaddingApplied) {
+                topPadding += mTopPadding;
+                bottomPadding += mBottomPadding;
+                mPaddingApplied = true;
+            }
+        } else {
+            if (mPaddingApplied) {
+                topPadding -= mTopPadding;
+                bottomPadding -= mBottomPadding;
+                mPaddingApplied = false;
+            }
+        }
+        int previousTop = getPaddingTop();
+        int previousBottom = getPaddingBottom();
+        if (previousTop != topPadding || previousBottom != bottomPadding) {
+            setPadding(getPaddingLeft(), topPadding, getPaddingRight(), bottomPadding);
+        }
+        super.addView(child);
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index ff769ad..683e603 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -382,6 +382,27 @@
          */
         public static final String COLUMN_ERROR_MSG = "errorMsg";
 
+        /**
+         *  This column stores the source of the last update to this row.
+         *  This column is only for internal use.
+         *  Valid values are indicated by LAST_UPDATESRC_* constants.
+         * <P>Type: INT</P>
+         */
+        public static final String COLUMN_LAST_UPDATESRC = "lastUpdateSrc";
+
+        /**
+         * default value for {@link #COLUMN_LAST_UPDATESRC}.
+         * This value is used when this column's value is not relevant.
+         */
+        public static final int LAST_UPDATESRC_NOT_RELEVANT = 0;
+
+        /**
+         * One of the values taken by {@link #COLUMN_LAST_UPDATESRC}.
+         * This value is used when the update is NOT to be relayed to the DownloadService
+         * (and thus spare DownloadService from scanning the database when this change occurs)
+         */
+        public static final int LAST_UPDATESRC_DONT_NOTIFY_DOWNLOADSVC = 1;
+
         /*
          * Lists the destinations that an application can specify for a download.
          */
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index f111ef2..fb4bed7 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -53,6 +53,13 @@
 
     private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
 
+   /**
+     * Broadcast Action:  A broadcast to indicate the end of an MTP session with the host.
+     * This broadcast is only sent if MTP activity has modified the media database during the
+     * most recent MTP session.
+     */
+    public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END";
+
     /**
      * Activity Action: Launch a music player.
      * The activity should be able to play, browse, or manipulate music files stored on the device.
diff --git a/core/java/android/provider/Mtp.java b/core/java/android/provider/Ptp.java
similarity index 89%
rename from core/java/android/provider/Mtp.java
rename to core/java/android/provider/Ptp.java
index 78110ef..2c54370 100644
--- a/core/java/android/provider/Mtp.java
+++ b/core/java/android/provider/Ptp.java
@@ -22,28 +22,20 @@
 
 
 /**
- * The MTP provider supports accessing content on MTP and PTP devices.
+ * The PTP provider supports accessing content on PTP devices.
  * @hide
  */
-public final class Mtp
+public final class Ptp
 {
-    private final static String TAG = "Mtp";
+    private final static String TAG = "Ptp";
 
-    public static final String AUTHORITY = "mtp";
+    public static final String AUTHORITY = "ptp";
 
     private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
     private static final String CONTENT_AUTHORITY_DEVICE_SLASH = "content://" + AUTHORITY + "/device/";
 
-
-   /**
-     * Broadcast Action:  A broadcast to indicate the end of an MTP session with the host.
-     * This broadcast is only sent if MTP activity has modified the media database during the
-     * most recent MTP session
-     */
-    public static final String ACTION_MTP_SESSION_END = "android.provider.action.MTP_SESSION_END";
-
     /**
-     * Contains list of all MTP/PTP devices
+     * Contains list of all PTP devices
      */
     public static final class Device implements BaseColumns {
 
@@ -67,7 +59,7 @@
     }
 
     /**
-     * Contains list of storage units for an MTP/PTP device
+     * Contains list of storage units for an PTP device
      */
     public static final class Storage implements BaseColumns {
 
@@ -93,7 +85,7 @@
     }
 
     /**
-     * Contains list of objects on an MTP/PTP device
+     * Contains list of objects on an PTP device
      */
     public static final class Object implements BaseColumns {
 
@@ -133,7 +125,7 @@
 
         /**
          * The following columns correspond to the fields in the ObjectInfo dataset
-         * as described in the MTP specification.
+         * as described in the PTP specification.
          */
 
         /**
@@ -144,7 +136,7 @@
 
         /**
          * The object's format.  Can be one of the FORMAT_* symbols below,
-         * or any of the valid MTP object formats as defined in the MTP specification.
+         * or any of the valid PTP object formats as defined in the PTP specification.
          * <P>Type: INTEGER</P>
          */
         public static final String FORMAT = "format";
@@ -163,7 +155,7 @@
 
         /**
          * The object's thumbnail format.  Can be one of the FORMAT_* symbols below,
-         * or any of the valid MTP object formats as defined in the MTP specification.
+         * or any of the valid PTP object formats as defined in the PTP specification.
          * <P>Type: INTEGER</P>
          */
         public static final String THUMB_FORMAT = "thumb_format";
diff --git a/core/java/android/util/PrefixPrinter.java b/core/java/android/util/PrefixPrinter.java
new file mode 100644
index 0000000..62f7da1
--- /dev/null
+++ b/core/java/android/util/PrefixPrinter.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * PrefixPrinter is a Printer which prefixes all lines with a given
+ * prefix.
+ *
+ * @hide
+ */
+public class PrefixPrinter implements Printer {
+    private final Printer mPrinter;
+    private final String mPrefix;
+
+    /**
+     * Creates a new PrefixPrinter.
+     *
+     * <p>If prefix is null or empty, the provided printer is returned, rather
+     * than making a prefixing printer.
+     */
+    public static Printer create(Printer printer, String prefix) {
+        if (prefix == null || prefix.equals("")) {
+            return printer;
+        }
+        return new PrefixPrinter(printer, prefix);
+    }
+
+    private PrefixPrinter(Printer printer, String prefix) {
+        mPrinter = printer;
+        mPrefix = prefix;
+    }
+
+    public void println(String str) {
+        mPrinter.println(mPrefix + str);
+    }
+}
diff --git a/core/java/android/util/Singleton.java b/core/java/android/util/Singleton.java
new file mode 100644
index 0000000..8a38bdb
--- /dev/null
+++ b/core/java/android/util/Singleton.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+/**
+ * Singleton helper class for lazily initialization.
+ *
+ * Modeled after frameworks/base/include/utils/Singleton.h
+ *
+ * @hide
+ */
+public abstract class Singleton<T> {
+    private T mInstance;
+
+    protected abstract T create();
+
+    public final T get() {
+        synchronized (this) {
+            if (mInstance == null) {
+                mInstance = create();
+            }
+            return mInstance;
+        }
+    }
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e6eb46e..011ad77 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1627,6 +1627,40 @@
     static final int ACTIVATED                    = 0x40000000;
 
     /**
+     * Always allow a user to over-scroll this view, provided it is a
+     * view that can scroll.
+     *
+     * @see #getOverScrollMode()
+     * @see #setOverScrollMode(int)
+     */
+    public static final int OVER_SCROLL_ALWAYS = 0;
+
+    /**
+     * Allow a user to over-scroll this view only if the content is large
+     * enough to meaningfully scroll, provided it is a view that can scroll.
+     *
+     * @see #getOverScrollMode()
+     * @see #setOverScrollMode(int)
+     */
+    public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1;
+
+    /**
+     * Never allow a user to over-scroll this view.
+     *
+     * @see #getOverScrollMode()
+     * @see #setOverScrollMode(int)
+     */
+    public static final int OVER_SCROLL_NEVER = 2;
+
+    /**
+     * Controls the over-scroll mode for this view.
+     * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
+     * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
+     * and {@link #OVER_SCROLL_NEVER}.
+     */
+    private int mOverScrollMode;
+
+    /**
      * The parent this view is attached to.
      * {@hide}
      *
@@ -2057,6 +2091,7 @@
         mResources = context != null ? context.getResources() : null;
         mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+        setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
     }
 
     /**
@@ -2122,6 +2157,7 @@
 
         int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY;
 
+        int overScrollMode = mOverScrollMode;
         final int N = a.getIndexCount();
         for (int i = 0; i < N; i++) {
             int attr = a.getIndex(i);
@@ -2327,9 +2363,14 @@
                         });
                     }
                     break;
+                case R.styleable.View_overScrollMode:
+                    overScrollMode = a.getInt(attr, OVER_SCROLL_IF_CONTENT_SCROLLS);
+                    break;
             }
         }
 
+        setOverScrollMode(overScrollMode);
+
         if (background != null) {
             setBackgroundDrawable(background);
         }
@@ -10131,6 +10172,128 @@
     }
 
     /**
+     * Scroll the view with standard behavior for scrolling beyond the normal
+     * content boundaries. Views that call this method should override
+     * {@link #onOverScrolled(int, int, boolean, boolean)} to respond to the
+     * results of an over-scroll operation.
+     *
+     * Views can use this method to handle any touch or fling-based scrolling.
+     *
+     * @param deltaX Change in X in pixels
+     * @param deltaY Change in Y in pixels
+     * @param scrollX Current X scroll value in pixels before applying deltaX
+     * @param scrollY Current Y scroll value in pixels before applying deltaY
+     * @param scrollRangeX Maximum content scroll range along the X axis
+     * @param scrollRangeY Maximum content scroll range along the Y axis
+     * @param maxOverScrollX Number of pixels to overscroll by in either direction
+     *          along the X axis.
+     * @param maxOverScrollY Number of pixels to overscroll by in either direction
+     *          along the Y axis.
+     * @param isTouchEvent true if this scroll operation is the result of a touch event.
+     * @return true if scrolling was clamped to an over-scroll boundary along either
+     *          axis, false otherwise.
+     */
+    protected boolean overScrollBy(int deltaX, int deltaY,
+            int scrollX, int scrollY,
+            int scrollRangeX, int scrollRangeY,
+            int maxOverScrollX, int maxOverScrollY,
+            boolean isTouchEvent) {
+        final int overScrollMode = mOverScrollMode;
+        final boolean canScrollHorizontal =
+                computeHorizontalScrollRange() > computeHorizontalScrollExtent();
+        final boolean canScrollVertical =
+                computeVerticalScrollRange() > computeVerticalScrollExtent();
+        final boolean overScrollHorizontal = overScrollMode == OVER_SCROLL_ALWAYS ||
+                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal);
+        final boolean overScrollVertical = overScrollMode == OVER_SCROLL_ALWAYS ||
+                (overScrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical);
+
+        int newScrollX = scrollX + deltaX;
+        if (!overScrollHorizontal) {
+            maxOverScrollX = 0;
+        }
+
+        int newScrollY = scrollY + deltaY;
+        if (!overScrollVertical) {
+            maxOverScrollY = 0;
+        }
+
+        // Clamp values if at the limits and record
+        final int left = -maxOverScrollX;
+        final int right = maxOverScrollX + scrollRangeX;
+        final int top = -maxOverScrollY;
+        final int bottom = maxOverScrollY + scrollRangeY;
+
+        boolean clampedX = false;
+        if (newScrollX > right) {
+            newScrollX = right;
+            clampedX = true;
+        } else if (newScrollX < left) {
+            newScrollX = left;
+            clampedX = true;
+        }
+
+        boolean clampedY = false;
+        if (newScrollY > bottom) {
+            newScrollY = bottom;
+            clampedY = true;
+        } else if (newScrollY < top) {
+            newScrollY = top;
+            clampedY = true;
+        }
+
+        onOverScrolled(newScrollX, newScrollY, clampedX, clampedY);
+
+        return clampedX || clampedY;
+    }
+
+    /**
+     * Called by {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)} to
+     * respond to the results of an over-scroll operation.
+     *
+     * @param scrollX New X scroll value in pixels
+     * @param scrollY New Y scroll value in pixels
+     * @param clampedX True if scrollX was clamped to an over-scroll boundary
+     * @param clampedY True if scrollY was clamped to an over-scroll boundary
+     */
+    protected void onOverScrolled(int scrollX, int scrollY,
+            boolean clampedX, boolean clampedY) {
+        // Intentionally empty.
+    }
+
+    /**
+     * Returns the over-scroll mode for this view. The result will be
+     * one of {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+     * (allow over-scrolling only if the view content is larger than the container),
+     * or {@link #OVER_SCROLL_NEVER}.
+     *
+     * @return This view's over-scroll mode.
+     */
+    public int getOverScrollMode() {
+        return mOverScrollMode;
+    }
+
+    /**
+     * Set the over-scroll mode for this view. Valid over-scroll modes are
+     * {@link #OVER_SCROLL_ALWAYS} (default), {@link #OVER_SCROLL_IF_CONTENT_SCROLLS}
+     * (allow over-scrolling only if the view content is larger than the container),
+     * or {@link #OVER_SCROLL_NEVER}.
+     *
+     * Setting the over-scroll mode of a view will have an effect only if the
+     * view is capable of scrolling.
+     *
+     * @param overScrollMode The new over-scroll mode for this view.
+     */
+    public void setOverScrollMode(int overScrollMode) {
+        if (overScrollMode != OVER_SCROLL_ALWAYS &&
+                overScrollMode != OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                overScrollMode != OVER_SCROLL_NEVER) {
+            throw new IllegalArgumentException("Invalid overscroll mode " + overScrollMode);
+        }
+        mOverScrollMode = overScrollMode;
+    }
+
+    /**
      * A MeasureSpec encapsulates the layout requirements passed from parent to child.
      * Each MeasureSpec represents a requirement for either the width or the height.
      * A MeasureSpec is comprised of a size and a mode. There are three possible
diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java
index 85981d2..bb85894 100644
--- a/core/java/android/view/ViewConfiguration.java
+++ b/core/java/android/view/ViewConfiguration.java
@@ -144,7 +144,7 @@
     /**
      * Maximum velocity to initiate a fling, as measured in pixels per second
      */
-    private static final int MAXIMUM_FLING_VELOCITY = 4000;
+    private static final int MAXIMUM_FLING_VELOCITY = 8000;
 
     /**
      * The maximum size of View's drawing cache, expressed in bytes. This size
@@ -158,6 +158,16 @@
      */
     private static float SCROLL_FRICTION = 0.015f;
 
+    /**
+     * Max distance to overscroll for edge effects
+     */
+    private static final int OVERSCROLL_DISTANCE = 0;
+
+    /**
+     * Max distance to overfling for edge effects
+     */
+    private static final int OVERFLING_DISTANCE = 4;
+
     private final int mEdgeSlop;
     private final int mFadingEdgeLength;
     private final int mMinimumFlingVelocity;
@@ -168,6 +178,8 @@
     private final int mDoubleTapSlop;
     private final int mWindowTouchSlop;
     private final int mMaximumDrawingCacheSize;
+    private final int mOverscrollDistance;
+    private final int mOverflingDistance;
 
     private static final SparseArray<ViewConfiguration> sConfigurations =
             new SparseArray<ViewConfiguration>(2);
@@ -188,6 +200,8 @@
         mWindowTouchSlop = WINDOW_TOUCH_SLOP;
         //noinspection deprecation
         mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE;
+        mOverscrollDistance = OVERSCROLL_DISTANCE;
+        mOverflingDistance = OVERFLING_DISTANCE;
     }
 
     /**
@@ -216,6 +230,9 @@
 
         // Size of the screen in bytes, in ARGB_8888 format
         mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels;
+
+        mOverscrollDistance = (int) (density * OVERSCROLL_DISTANCE + 0.5f);
+        mOverflingDistance = (int) (density * OVERFLING_DISTANCE + 0.5f);
     }
 
     /**
@@ -473,6 +490,20 @@
     }
 
     /**
+     * @return The maximum distance a View should overscroll by when showing edge effects.
+     */
+    public int getScaledOverscrollDistance() {
+        return mOverscrollDistance;
+    }
+
+    /**
+     * @return The maximum distance a View should overfling by when showing edge effects.
+     */
+    public int getScaledOverflingDistance() {
+        return mOverflingDistance;
+    }
+
+    /**
      * The amount of time that the zoom controls should be
      * displayed on the screen expressed in milliseconds.
      * 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index a5f3ade..9bc1c22 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -23,7 +23,6 @@
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
@@ -49,7 +48,9 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -1277,10 +1278,10 @@
             }
         }
     }
-    
+
     /**
-     * Force switch to a new input method component.  This can only be called
-     * from the currently active input method, as validated by the given token.
+     * Force switch to a new input method component. This can only be called
+     * from an application or a service which has a token of the currently active input method.
      * @param token Supplies the identifying token given to an input method
      * when it was started, which allows it to perform this operation on
      * itself.
@@ -1293,7 +1294,24 @@
             throw new RuntimeException(e);
         }
     }
-    
+
+    /**
+     * Force switch to a new input method and subtype. This can only be called
+     * from an application or a service which has a token of the currently active input method.
+     * @param token Supplies the identifying token given to an input method
+     * when it was started, which allows it to perform this operation on
+     * itself.
+     * @param id The unique identifier for the new input method to be switched to.
+     * @param subtype The new subtype of the new input method to be switched to.
+     */
+    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+        try {
+            mService.setInputMethodAndSubtype(token, id, subtype);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     /**
      * Close/hide the input method's soft input area, so the user no longer
      * sees it or can interact with it.  This can only be called
@@ -1454,30 +1472,29 @@
         }
     }
 
-    public List<Pair<InputMethodInfo, InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
+    public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
         synchronized (mH) {
-            List<Pair<InputMethodInfo, InputMethodSubtype>> ret =
-                    new ArrayList<Pair<InputMethodInfo, InputMethodSubtype>>();
+            HashMap<InputMethodInfo, List<InputMethodSubtype>> ret =
+                    new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
             try {
                 // TODO: We should change the return type from List<Object> to List<Parcelable>
                 List<Object> info = mService.getShortcutInputMethodsAndSubtypes();
-                // "info" has imi1, subtype1, imi2, subtype2, imi3, subtype3,..... in the list
-                Object imi;
-                Object subtype;
-                if (info != null && info.size() > 0) {
-                    final int N = info.size();
-                    if (N % 2 == 0) {
-                        for (int i = 0; i < N;) {
-                            if ((imi = info.get(i++)) instanceof InputMethodInfo) {
-                                subtype = info.get(i++);
-                                ret.add(new Pair<InputMethodInfo, InputMethodSubtype> (
-                                        (InputMethodInfo)imi,
-                                        (subtype instanceof InputMethodSubtype) ?
-                                                (InputMethodSubtype)subtype : null));
+                // "info" has imi1, subtype1, subtype2, imi2, subtype2, imi3, subtype3..in the list
+                ArrayList<InputMethodSubtype> subtypes = null;
+                final int N = info.size();
+                if (info != null && N > 0) {
+                    for (int i = 0; i < N; ++i) {
+                        Object o = info.get(i);
+                        if (o instanceof InputMethodInfo) {
+                            if (ret.containsKey(o)) {
+                                Log.e(TAG, "IMI list already contains the same InputMethod.");
+                                break;
                             }
+                            subtypes = new ArrayList<InputMethodSubtype>();
+                            ret.put((InputMethodInfo)o, subtypes);
+                        } else if (subtypes != null && o instanceof InputMethodSubtype) {
+                            subtypes.add((InputMethodSubtype)o);
                         }
-                    } else {
-                        Log.w(TAG, "The size of list was illegal.");
                     }
                 }
             } catch (RemoteException e) {
@@ -1486,6 +1503,7 @@
             return ret;
         }
     }
+
     public boolean switchToLastInputMethod(IBinder imeToken) {
         synchronized (mH) {
             try {
diff --git a/core/java/android/webkit/OverScrollGlow.java b/core/java/android/webkit/OverScrollGlow.java
new file mode 100644
index 0000000..53600f6
--- /dev/null
+++ b/core/java/android/webkit/OverScrollGlow.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.webkit;
+
+import com.android.internal.R;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+import android.widget.EdgeGlow;
+
+/**
+ * This class manages the edge glow effect when a WebView is flung or pulled beyond the edges.
+ * @hide
+ */
+public class OverScrollGlow {
+    private WebView mHostView;
+
+    private EdgeGlow mEdgeGlowTop;
+    private EdgeGlow mEdgeGlowBottom;
+    private EdgeGlow mEdgeGlowLeft;
+    private EdgeGlow mEdgeGlowRight;
+
+    private int mOverScrollDeltaX;
+    private int mOverScrollDeltaY;
+
+    public OverScrollGlow(WebView host) {
+        mHostView = host;
+        final Resources res = host.getContext().getResources();
+        final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+        final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+        mEdgeGlowTop = new EdgeGlow(edge, glow);
+        mEdgeGlowBottom = new EdgeGlow(edge, glow);
+        mEdgeGlowLeft = new EdgeGlow(edge, glow);
+        mEdgeGlowRight = new EdgeGlow(edge, glow);
+    }
+
+    /**
+     * Pull leftover touch scroll distance into one of the edge glows as appropriate.
+     *
+     * @param x Current X scroll offset
+     * @param y Current Y scroll offset
+     * @param oldX Old X scroll offset
+     * @param oldY Old Y scroll offset
+     * @param maxX Maximum range for horizontal scrolling
+     * @param maxY Maximum range for vertical scrolling
+     */
+    public void pullGlow(int x, int y, int oldX, int oldY, int maxX, int maxY) {
+        // Only show overscroll bars if there was no movement in any direction
+        // as a result of scrolling.
+        if (oldX == mHostView.getScrollX() && oldY == mHostView.getScrollY()) {
+            // Don't show left/right glows if we fit the whole content.
+            // Also don't show if there was vertical movement.
+            if (maxX > 0) {
+                final int pulledToX = oldX + mOverScrollDeltaX;
+                if (pulledToX < 0) {
+                    mEdgeGlowLeft.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
+                    if (!mEdgeGlowRight.isFinished()) {
+                        mEdgeGlowRight.onRelease();
+                    }
+                } else if (pulledToX > maxX) {
+                    mEdgeGlowRight.onPull((float) mOverScrollDeltaX / mHostView.getWidth());
+                    if (!mEdgeGlowLeft.isFinished()) {
+                        mEdgeGlowLeft.onRelease();
+                    }
+                }
+                mOverScrollDeltaX = 0;
+            }
+
+            if (maxY > 0 || mHostView.getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
+                final int pulledToY = oldY + mOverScrollDeltaY;
+                if (pulledToY < 0) {
+                    mEdgeGlowTop.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
+                    if (!mEdgeGlowBottom.isFinished()) {
+                        mEdgeGlowBottom.onRelease();
+                    }
+                } else if (pulledToY > maxY) {
+                    mEdgeGlowBottom.onPull((float) mOverScrollDeltaY / mHostView.getHeight());
+                    if (!mEdgeGlowTop.isFinished()) {
+                        mEdgeGlowTop.onRelease();
+                    }
+                }
+                mOverScrollDeltaY = 0;
+            }
+        }
+    }
+
+    /**
+     * Set touch delta values indicating the current amount of overscroll.
+     *
+     * @param deltaX
+     * @param deltaY
+     */
+    public void setOverScrollDeltas(int deltaX, int deltaY) {
+        mOverScrollDeltaX = deltaX;
+        mOverScrollDeltaY = deltaY;
+    }
+
+    /**
+     * Absorb leftover fling velocity into one of the edge glows as appropriate.
+     *
+     * @param x Current X scroll offset
+     * @param y Current Y scroll offset
+     * @param oldX Old X scroll offset
+     * @param oldY Old Y scroll offset
+     * @param rangeX Maximum range for horizontal scrolling
+     * @param rangeY Maximum range for vertical scrolling
+     */
+    public void absorbGlow(int x, int y, int oldX, int oldY, int rangeX, int rangeY) {
+        if (rangeY > 0 || mHostView.getOverScrollMode() == View.OVER_SCROLL_ALWAYS) {
+            if (y < 0 && oldY >= 0) {
+                mEdgeGlowTop.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
+                if (!mEdgeGlowBottom.isFinished()) {
+                    mEdgeGlowBottom.onRelease();
+                }
+            } else if (y > rangeY && oldY <= rangeY) {
+                mEdgeGlowBottom.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
+                if (!mEdgeGlowTop.isFinished()) {
+                    mEdgeGlowTop.onRelease();
+                }
+            }
+        }
+
+        if (rangeX > 0) {
+            if (x < 0 && oldX >= 0) {
+                mEdgeGlowLeft.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
+                if (!mEdgeGlowRight.isFinished()) {
+                    mEdgeGlowRight.onRelease();
+                }
+            } else if (x > rangeX && oldX <= rangeX) {
+                mEdgeGlowRight.onAbsorb((int) mHostView.mScroller.getCurrVelocity());
+                if (!mEdgeGlowLeft.isFinished()) {
+                    mEdgeGlowLeft.onRelease();
+                }
+            }
+        }
+    }
+
+    /**
+     * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null.
+     *
+     * @param canvas Canvas to draw into, transformed into view coordinates.
+     * @return true if glow effects are still animating and the view should invalidate again.
+     */
+    public boolean drawEdgeGlows(Canvas canvas) {
+        final int scrollX = mHostView.getScrollX();
+        final int scrollY = mHostView.getScrollY();
+        final int width = mHostView.getWidth();
+        int height = mHostView.getHeight();
+
+        boolean invalidateForGlow = false;
+        if (!mEdgeGlowTop.isFinished()) {
+            final int restoreCount = canvas.save();
+
+            canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY));
+            mEdgeGlowTop.setSize(width * 2, height);
+            invalidateForGlow |= mEdgeGlowTop.draw(canvas);
+            canvas.restoreToCount(restoreCount);
+        }
+        if (!mEdgeGlowBottom.isFinished()) {
+            final int restoreCount = canvas.save();
+
+            canvas.translate(-width / 2 + scrollX,
+                    Math.max(mHostView.computeMaxScrollY(), scrollY) + height);
+            canvas.rotate(180, width, 0);
+            mEdgeGlowBottom.setSize(width * 2, height);
+            invalidateForGlow |= mEdgeGlowBottom.draw(canvas);
+            canvas.restoreToCount(restoreCount);
+        }
+        if (!mEdgeGlowLeft.isFinished()) {
+            final int restoreCount = canvas.save();
+
+            canvas.rotate(270);
+            canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX));
+            mEdgeGlowLeft.setSize(height * 2, width);
+            invalidateForGlow |= mEdgeGlowLeft.draw(canvas);
+            canvas.restoreToCount(restoreCount);
+        }
+        if (!mEdgeGlowRight.isFinished()) {
+            final int restoreCount = canvas.save();
+
+            canvas.rotate(90);
+            canvas.translate(-height / 2 + scrollY,
+                    -(Math.max(mHostView.computeMaxScrollX(), scrollX) + width));
+            mEdgeGlowRight.setSize(height * 2, width);
+            invalidateForGlow |= mEdgeGlowRight.draw(canvas);
+            canvas.restoreToCount(restoreCount);
+        }
+        return invalidateForGlow;
+    }
+
+    /**
+     * @return True if any glow is still animating
+     */
+    public boolean isAnimating() {
+        return (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() ||
+                !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished());
+    }
+
+    /**
+     * Release all glows from any touch pulls in progress.
+     */
+    public void releaseAll() {
+        mEdgeGlowTop.onRelease();
+        mEdgeGlowBottom.onRelease();
+        mEdgeGlowLeft.onRelease();
+        mEdgeGlowRight.onRelease();
+    }
+}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index b2ba7e23..755366c 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -330,4 +330,14 @@
      */
     public void setInstallableWebApp() { }
 
+    /**
+     * Tell the client that the page being viewed has an autofillable
+     * form and the user would like to set a profile up.
+     * @param msg A Message to send once the user has successfully
+     *      set up a profile and to inform the WebTextView it should
+     *      now autofill using that new profile.
+     * @hide
+     */
+    public void setupAutoFill(Message msg) { }
+
 }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index f4caa74..2e69d99 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -268,6 +268,8 @@
 
     private AutoFillProfile mAutoFillProfile;
 
+    private boolean         mUseWebViewBackgroundForOverscroll = true;
+
     // private WebSettings, not accessible by the host activity
     static private int      mDoubleTapToastCount = 3;
 
@@ -631,6 +633,23 @@
     }
 
     /**
+     * Set whether the WebView uses its background for over scroll background.
+     * If true, it will use the WebView's background. If false, it will use an
+     * internal pattern. Default is true.
+     */
+    public void setUseWebViewBackgroundForOverscrollBackground(boolean view) {
+        mUseWebViewBackgroundForOverscroll = view;
+    }
+
+    /**
+     * Returns true if this WebView uses WebView's background instead of
+     * internal pattern for over scroll background.
+     */
+    public boolean getUseWebViewBackgroundForOverscrollBackground() {
+        return mUseWebViewBackgroundForOverscroll;
+    }
+
+    /**
      * Store whether the WebView is saving form data.
      */
     public void setSaveFormData(boolean save) {
@@ -1626,6 +1645,13 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    public synchronized AutoFillProfile getAutoFillProfile() {
+        return mAutoFillProfile;
+    }
+
     int getDoubleTapToastCount() {
         return mDoubleTapToastCount;
     }
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index fafb6be..e1a5c2d 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -26,6 +26,8 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Message;
 import android.text.BoringLayout.Metrics;
 import android.text.DynamicLayout;
 import android.text.Editable;
@@ -129,6 +131,7 @@
 
     private boolean mAutoFillable; // Is this textview part of an autofillable form?
     private int mQueryId;
+    private boolean mAutoFillProfileIsSet;
 
     // Types used with setType.  Keep in sync with CachedInput.h
     private static final int NORMAL_TEXT_FIELD = 0;
@@ -140,6 +143,9 @@
     private static final int TELEPHONE = 6;
     private static final int URL = 7;
 
+    private static final int AUTOFILL_FORM = 100;
+    private Handler mHandler;
+
     /**
      * Create a new WebTextView.
      * @param   context The Context for this WebTextView.
@@ -163,6 +169,18 @@
         setTextColor(DebugFlags.DRAW_WEBTEXTVIEW ? Color.RED : Color.BLACK);
         // This helps to align the text better with the text in the web page.
         setIncludeFontPadding(false);
+
+        mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                case AUTOFILL_FORM:
+                    mWebView.autoFillForm(mQueryId);
+                    break;
+                }
+            }
+        };
+
     }
 
     public void setAutoFillable(int queryId) {
@@ -801,8 +819,17 @@
                     if (id == 0 && position == 0) {
                         // Blank out the text box while we wait for WebCore to fill the form.
                         replaceText("");
-                        // Call a webview method to tell WebCore to autofill the form.
-                        mWebView.autoFillForm(mQueryId);
+                        WebSettings settings = mWebView.getSettings();
+                        if (mAutoFillProfileIsSet) {
+                            // Call a webview method to tell WebCore to autofill the form.
+                            mWebView.autoFillForm(mQueryId);
+                        } else {
+                            // There is no autofill profile setup yet and the user has
+                            // elected to try and set one up. Call through to the
+                            // embedder to action that.
+                            mWebView.getWebChromeClient().setupAutoFill(
+                                    mHandler.obtainMessage(AUTOFILL_FORM));
+                        }
                     }
                 }
             });
@@ -1124,4 +1151,8 @@
     /* package */ void updateCachedTextfield() {
         mWebView.updateCachedTextfield(getText().toString());
     }
+
+    /* package */ void setAutoFillProfileIsSet(boolean autoFillProfileIsSet) {
+        mAutoFillProfileIsSet = autoFillProfileIsSet;
+    }
 }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 602975f..a0ee765 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -16,19 +16,25 @@
 
 package android.webkit;
 
+import com.android.internal.R;
+
 import android.annotation.Widget;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.DialogInterface;
-import android.content.IntentFilter;
 import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.CornerPathEffect;
@@ -40,6 +46,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.net.http.SslCertificate;
@@ -81,13 +88,12 @@
 import android.widget.AdapterView.OnItemClickListener;
 import android.widget.ArrayAdapter;
 import android.widget.CheckedTextView;
+import android.widget.EdgeGlow;
 import android.widget.LinearLayout;
 import android.widget.ListView;
-import android.widget.Scroller;
+import android.widget.OverScroller;
 import android.widget.Toast;
 
-import junit.framework.Assert;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -102,6 +108,8 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import junit.framework.Assert;
+
 /**
  * <p>A View that displays web pages. This class is the basis upon which you
  * can roll your own web browser or simply display some online content within your Activity.
@@ -529,7 +537,13 @@
     // time for the longest scroll animation
     private static final int MAX_DURATION = 750;   // milliseconds
     private static final int SLIDE_TITLE_DURATION = 500;   // milliseconds
-    private Scroller mScroller;
+
+    // Used by OverScrollGlow
+    OverScroller mScroller;
+
+    private boolean mInOverScrollMode = false;
+    private static Paint mOverScrollBackground;
+    private static Paint mOverScrollBorder;
 
     private boolean mWrapContent;
     private static final int MOTIONLESS_FALSE           = 0;
@@ -734,6 +748,20 @@
     // variable to cache the above pattern in case accessibility is enabled.
     private Pattern mMatchAxsUrlParameterPattern;
 
+    /**
+     * Max distance to overscroll by in pixels.
+     * This how far content can be pulled beyond its normal bounds by the user.
+     */
+    private int mOverscrollDistance;
+
+    /**
+     * Max distance to overfling by in pixels.
+     * This is how far flinged content can move beyond the end of its normal bounds.
+     */
+    private int mOverflingDistance;
+
+    private OverScrollGlow mOverScrollGlow;
+
     // Used to match key downs and key ups
     private boolean mGotKeyDown;
 
@@ -909,7 +937,7 @@
         L10nUtils.loadStrings(context);
         mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
         mDatabase = WebViewDatabase.getInstance(context);
-        mScroller = new Scroller(context);
+        mScroller = new OverScroller(context, null, 0, 0, false); //TODO Use OverScroller's flywheel
         mZoomManager = new ZoomManager(this, mCallbackProxy);
 
         /* The init method must follow the creation of certain member variables,
@@ -1044,6 +1072,9 @@
 
         // Compute the inverse of the density squared.
         DRAG_LAYER_INVERSE_DENSITY_SQUARED = 1 / (density * density);
+
+        mOverscrollDistance = configuration.getScaledOverscrollDistance();
+        mOverflingDistance = configuration.getScaledOverflingDistance();
     }
 
     /**
@@ -1066,6 +1097,18 @@
                 new TextToSpeech(getContext(), null));
     }
 
+    @Override
+    public void setOverScrollMode(int mode) {
+        super.setOverScrollMode(mode);
+        if (mode != OVER_SCROLL_NEVER) {
+            if (mOverScrollGlow == null) {
+                mOverScrollGlow = new OverScrollGlow(this);
+            }
+        } else {
+            mOverScrollGlow = null;
+        }
+    }
+
     /* package */void updateDefaultZoomDensity(int zoomDensity) {
         final float density = mContext.getResources().getDisplayMetrics().density
                 * 100 / zoomDensity;
@@ -1197,7 +1240,8 @@
      * @hide
      */
     public int getVisibleTitleHeight() {
-        return Math.max(getTitleHeight() - mScrollY, 0);
+        // need to restrict mScrollY due to over scroll
+        return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0);
     }
 
     /*
@@ -1946,7 +1990,7 @@
         }
         nativeClearCursor(); // start next trackball movement from page edge
         if (bottom) {
-            return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0);
+            return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0);
         }
         // Page down.
         int h = getHeight();
@@ -2171,13 +2215,15 @@
 
     // Expects x in view coordinates
     int pinLocX(int x) {
-        return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
+        if (mInOverScrollMode) return x;
+        return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange());
     }
 
     // Expects y in view coordinates
     int pinLocY(int y) {
+        if (mInOverScrollMode) return y;
         return pinLoc(y, getViewHeightWithTitle(),
-                      computeVerticalScrollRange() + getTitleHeight());
+                      computeRealVerticalScrollRange() + getTitleHeight());
     }
 
     /**
@@ -2412,7 +2458,7 @@
     // Sets r to be our visible rectangle in content coordinates
     private void calcOurContentVisibleRect(Rect r) {
         calcOurVisibleRect(r);
-        // pin the rect to the bounds of the content
+        // since we might overscroll, pin the rect to the bounds of the content
         r.left = Math.max(viewToContentX(r.left), 0);
         // viewToContentY will remove the total height of the title bar.  Add
         // the visible height back in to account for the fact that if the title
@@ -2497,8 +2543,7 @@
         return false;
     }
 
-    @Override
-    protected int computeHorizontalScrollRange() {
+    private int computeRealHorizontalScrollRange() {
         if (mDrawHistory) {
             return mHistoryWidth;
         } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF
@@ -2512,7 +2557,27 @@
     }
 
     @Override
-    protected int computeVerticalScrollRange() {
+    protected int computeHorizontalScrollRange() {
+        int range = computeRealHorizontalScrollRange();
+
+        // Adjust reported range if overscrolled to compress the scroll bars
+        final int scrollX = mScrollX;
+        final int overscrollRight = computeMaxScrollX();
+        if (scrollX < 0) {
+            range -= scrollX;
+        } else if (scrollX > overscrollRight) {
+            range += scrollX - overscrollRight;
+        }
+
+        return range;
+    }
+
+    @Override
+    protected int computeHorizontalScrollOffset() {
+        return Math.max(mScrollX, 0);
+    }
+
+    private int computeRealVerticalScrollRange() {
         if (mDrawHistory) {
             return mHistoryHeight;
         } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
@@ -2526,6 +2591,22 @@
     }
 
     @Override
+    protected int computeVerticalScrollRange() {
+        int range = computeRealVerticalScrollRange();
+
+        // Adjust reported range if overscrolled to compress the scroll bars
+        final int scrollY = mScrollY;
+        final int overscrollBottom = computeMaxScrollY();
+        if (scrollY < 0) {
+            range -= scrollY;
+        } else if (scrollY > overscrollBottom) {
+            range += scrollY - overscrollBottom;
+        }
+
+        return range;
+    }
+
+    @Override
     protected int computeVerticalScrollOffset() {
         return Math.max(mScrollY - getTitleHeight(), 0);
     }
@@ -2540,10 +2621,39 @@
     protected void onDrawVerticalScrollBar(Canvas canvas,
                                            Drawable scrollBar,
                                            int l, int t, int r, int b) {
+        if (mScrollY < 0) {
+            t -= mScrollY;
+        }
         scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b);
         scrollBar.draw(canvas);
     }
 
+    @Override
+    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX,
+            boolean clampedY) {
+        mInOverScrollMode = false;
+        int maxX = computeMaxScrollX();
+        int maxY = computeMaxScrollY();
+        if (maxX == 0) {
+            // do not over scroll x if the page just fits the screen
+            scrollX = pinLocX(scrollX);
+        } else if (scrollX < 0 || scrollX > maxX) {
+            mInOverScrollMode = true;
+        }
+        if (scrollY < 0 || scrollY > maxY) {
+            mInOverScrollMode = true;
+        }
+
+        int oldX = mScrollX;
+        int oldY = mScrollY;
+
+        super.scrollTo(scrollX, scrollY);
+
+        if (mOverScrollGlow != null) {
+            mOverScrollGlow.pullGlow(mScrollX, mScrollY, oldX, oldY, maxX, maxY);
+        }
+    }
+
     /**
      * Get the url for the current page. This is not always the same as the url
      * passed to WebViewClient.onPageStarted because although the load for
@@ -2923,13 +3033,23 @@
         if (mScroller.computeScrollOffset()) {
             int oldX = mScrollX;
             int oldY = mScrollY;
-            mScrollX = mScroller.getCurrX();
-            mScrollY = mScroller.getCurrY();
-            postInvalidate();  // So we draw again
-            if (oldX != mScrollX || oldY != mScrollY) {
-                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-            } else if (mScroller.getStartX() != mScrollX
-                    || mScroller.getStartY() != mScrollY) {
+            int x = mScroller.getCurrX();
+            int y = mScroller.getCurrY();
+            invalidate();  // So we draw again
+
+            if (!mScroller.isFinished()) {
+                final int rangeX = computeMaxScrollX();
+                final int rangeY = computeMaxScrollY();
+                overScrollBy(x - oldX, y - oldY, oldX, oldY,
+                        rangeX, rangeY,
+                        mOverflingDistance, mOverflingDistance, false);
+
+                if (mOverScrollGlow != null) {
+                    mOverScrollGlow.absorbGlow(x, y, oldX, oldY, rangeX, rangeY);
+                }
+            } else {
+                mScrollX = x;
+                mScrollY = y;
                 abortAnimation();
                 mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
                 WebViewCore.resumePriority();
@@ -3436,6 +3556,40 @@
         drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing);
     }
 
+    /**
+     * Draw the background when beyond bounds
+     * @param canvas Canvas to draw into
+     */
+    private void drawOverScrollBackground(Canvas canvas) {
+        if (mOverScrollBackground == null) {
+            mOverScrollBackground = new Paint();
+            Bitmap bm = BitmapFactory.decodeResource(
+                    mContext.getResources(),
+                    com.android.internal.R.drawable.status_bar_background);
+            mOverScrollBackground.setShader(new BitmapShader(bm,
+                    Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
+            mOverScrollBorder = new Paint();
+            mOverScrollBorder.setStyle(Paint.Style.STROKE);
+            mOverScrollBorder.setStrokeWidth(0);
+            mOverScrollBorder.setColor(0xffbbbbbb);
+        }
+
+        int top = 0;
+        int right = computeRealHorizontalScrollRange();
+        int bottom = top + computeRealVerticalScrollRange();
+        // first draw the background and anchor to the top of the view
+        canvas.save();
+        canvas.translate(mScrollX, mScrollY);
+        canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom
+                - mScrollY, Region.Op.DIFFERENCE);
+        canvas.drawPaint(mOverScrollBackground);
+        canvas.restore();
+        // then draw the border
+        canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder);
+        // next clip the region for the content
+        canvas.clipRect(0, top, right, bottom);
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         // if mNativeClass is 0, the WebView has been destroyed. Do nothing.
@@ -3452,6 +3606,10 @@
         }
 
         int saveCount = canvas.save();
+        if (mInOverScrollMode && !getSettings()
+                .getUseWebViewBackgroundForOverscrollBackground()) {
+            drawOverScrollBackground(canvas);
+        }
         if (mTitleBar != null) {
             canvas.translate(0, (int) mTitleBar.getHeight());
         }
@@ -3466,6 +3624,10 @@
         }
         mWebViewCore.signalRepaintDone();
 
+        if (mOverScrollGlow != null && mOverScrollGlow.drawEdgeGlows(canvas)) {
+            invalidate();
+        }
+
         // paint the highlight in the end
         if (!mTouchHighlightRegion.isEmpty()) {
             if (mTouchHightlightPaint == null) {
@@ -3570,6 +3732,16 @@
         return false;
     }
 
+    private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        if (mSelectingText && mOrientation != newConfig.orientation) {
+            selectionDone();
+        }
+        mOrientation = newConfig.orientation;
+    }
+
     /**
      * Keep track of the Callback so we can end its ActionMode or remove its
      * titlebar.
@@ -4051,10 +4223,20 @@
                 // Note that code inside the adapter click handler in WebTextView depends
                 // on the AutoFill item being at the top of the drop down list. If you change
                 // the order, make sure to do it there too!
-                pastEntries.add(getResources().getText(
-                        com.android.internal.R.string.autofill_this_form).toString() +
-                        " " +
-                        mAutoFillData.getPreviewString());
+                WebSettings settings = getSettings();
+                if (settings != null && settings.getAutoFillProfile() != null) {
+                    pastEntries.add(getResources().getText(
+                            com.android.internal.R.string.autofill_this_form).toString() +
+                            " " +
+                            mAutoFillData.getPreviewString());
+                    mWebTextView.setAutoFillProfileIsSet(true);
+                } else {
+                    // There is no autofill profile set up yet, so add an option that
+                    // will invite the user to set their profile up.
+                    pastEntries.add(getResources().getText(
+                            com.android.internal.R.string.setup_autofill).toString());
+                    mWebTextView.setAutoFillProfileIsSet(false);
+                }
             }
 
             pastEntries.addAll(mDatabase.getFormData(mUrl, mName));
@@ -4697,12 +4879,14 @@
     @Override
     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
         super.onScrollChanged(l, t, oldl, oldt);
-        sendOurVisibleRect();
-        // update WebKit if visible title bar height changed. The logic is same
-        // as getVisibleTitleHeight.
-        int titleHeight = getTitleHeight();
-        if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
-            sendViewSizeZoom(false);
+        if (!mInOverScrollMode) {
+            sendOurVisibleRect();
+            // update WebKit if visible title bar height changed. The logic is same
+            // as getVisibleTitleHeight.
+            int titleHeight = getTitleHeight();
+            if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
+                sendViewSizeZoom(false);
+            }
         }
     }
 
@@ -4829,7 +5013,7 @@
 
         final ScaleGestureDetector detector =
                 mZoomManager.getMultiTouchGestureDetector();
-        boolean skipScaleGesture = false;
+        boolean isScrollGesture = false;
         // Set to the mid-point of a two-finger gesture used to detect if the
         // user has touched a layer.
         float gestureX = x;
@@ -4857,7 +5041,7 @@
                 }
                 action = ev.getActionMasked();
                 if (dist < DRAG_LAYER_FINGER_DISTANCE) {
-                    skipScaleGesture = true;
+                    isScrollGesture = true;
                 } else if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
                     // Fingers moved too far apart while dragging, the user
                     // might be trying to zoom.
@@ -4866,9 +5050,13 @@
             }
         }
 
-        // If the page disallows zoom, pass multi-pointer events to webkit.
-        if (!skipScaleGesture && ev.getPointerCount() > 1
-            && (mZoomManager.isZoomScaleFixed() || mDeferMultitouch)) {
+        // If the page disallows zoom, pass multi-touch events to webkit.
+        // mDeferMultitouch is a hack for layout tests, where it is used to
+        // force passing multi-touch events to webkit.
+        // FIXME: always pass multi-touch events to webkit and remove everything
+        // related to mDeferMultitouch.
+        if (ev.getPointerCount() > 1 &&
+                (mDeferMultitouch || (!isScrollGesture && mZoomManager.isZoomScaleFixed()))) {
             if (DebugFlags.WEB_VIEW) {
                 Log.v(LOGTAG, "passing " + ev.getPointerCount() + " points to webkit");
             }
@@ -4877,7 +5065,7 @@
         }
 
         if (mZoomManager.supportsMultiTouchZoom() && ev.getPointerCount() > 1 &&
-                mTouchMode != TOUCH_DRAG_LAYER_MODE && !skipScaleGesture) {
+                mTouchMode != TOUCH_DRAG_LAYER_MODE && !isScrollGesture) {
             if (!detector.isInProgress() &&
                     ev.getActionMasked() != MotionEvent.ACTION_POINTER_DOWN) {
                 // Insert a fake pointer down event in order to start
@@ -5150,27 +5338,13 @@
                     deltaX = 0;
                     deltaY = 0;
 
-                    if (skipScaleGesture) {
+                    if (isScrollGesture) {
                         startScrollingLayer(gestureX, gestureY);
                     }
                     startDrag();
                 }
 
                 // do pan
-                if (mTouchMode != TOUCH_DRAG_LAYER_MODE) {
-                    int newScrollX = pinLocX(mScrollX + deltaX);
-                    int newDeltaX = newScrollX - mScrollX;
-                    if (deltaX != newDeltaX) {
-                        deltaX = newDeltaX;
-                        fDeltaX = (float) newDeltaX;
-                    }
-                    int newScrollY = pinLocY(mScrollY + deltaY);
-                    int newDeltaY = newScrollY - mScrollY;
-                    if (deltaY != newDeltaY) {
-                        deltaY = newDeltaY;
-                        fDeltaY = (float) newDeltaY;
-                    }
-                }
                 boolean done = false;
                 boolean keepScrollBarsVisible = false;
                 if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) {
@@ -5360,6 +5534,12 @@
                             mHeldMotionless = MOTIONLESS_IGNORE;
                             doFling();
                             break;
+                        } else {
+                            if (mScroller.springBack(mScrollX, mScrollY, 0,
+                                    computeMaxScrollX(), 0,
+                                    computeMaxScrollY())) {
+                                invalidate();
+                            }
                         }
                         // redraw in high-quality, as we're done dragging
                         mHeldMotionless = MOTIONLESS_TRUE;
@@ -5380,6 +5560,8 @@
             }
             case MotionEvent.ACTION_CANCEL: {
                 if (mTouchMode == TOUCH_DRAG_MODE) {
+                    mScroller.springBack(mScrollX, mScrollY, 0,
+                            computeMaxScrollX(), 0, computeMaxScrollY());
                     invalidate();
                 }
                 cancelWebCoreTouchEvent(contentX, contentY, false);
@@ -5455,7 +5637,22 @@
                 }
                 return;
             }
-            scrollBy(deltaX, deltaY);
+
+            final int oldX = mScrollX;
+            final int oldY = mScrollY;
+            final int rangeX = computeMaxScrollX();
+            final int rangeY = computeMaxScrollY();
+
+            if (mOverScrollGlow != null) {
+                mOverScrollGlow.setOverScrollDeltas(deltaX, deltaY);
+            }
+
+            overScrollBy(deltaX, deltaY, oldX, oldY,
+                    rangeX, rangeY,
+                    mOverscrollDistance, mOverscrollDistance, true);
+            if (mOverScrollGlow != null && mOverScrollGlow.isAnimating()) {
+                invalidate();
+            }
         }
         mZoomManager.keepZoomPickerVisible();
     }
@@ -5468,6 +5665,11 @@
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
+
+        // Release any pulled glows
+        if (mOverScrollGlow != null) {
+            mOverScrollGlow.releaseAll();
+        }
     }
 
     private void cancelTouch() {
@@ -5478,6 +5680,7 @@
             mVelocityTracker.recycle();
             mVelocityTracker = null;
         }
+
         if (mTouchMode == TOUCH_DRAG_MODE ||
                 mTouchMode == TOUCH_DRAG_LAYER_MODE) {
             WebViewCore.resumePriority();
@@ -5789,12 +5992,20 @@
         }
     }
 
-    private int computeMaxScrollX() {
-        return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0);
+    /**
+     * Compute the maximum horizontal scroll position. Used by {@link OverScrollGlow}.
+     * @return Maximum horizontal scroll position within real content
+     */
+    int computeMaxScrollX() {
+        return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0);
     }
 
-    private int computeMaxScrollY() {
-        return Math.max(computeVerticalScrollRange() + getTitleHeight()
+    /**
+     * Compute the maximum vertical scroll position. Used by {@link OverScrollGlow}.
+     * @return Maximum vertical scroll position within real content
+     */
+    int computeMaxScrollY() {
+        return Math.max(computeRealVerticalScrollRange() + getTitleHeight()
                 - getViewHeightWithTitle(), 0);
     }
 
@@ -5813,7 +6024,7 @@
 
     public void flingScroll(int vx, int vy) {
         mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
-                computeMaxScrollY());
+                computeMaxScrollY(), mOverflingDistance, mOverflingDistance);
         invalidate();
     }
 
@@ -5843,6 +6054,10 @@
         if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
             WebViewCore.resumePriority();
             WebViewCore.resumeUpdatePicture(mWebViewCore);
+            if (mScroller.springBack(mScrollX, mScrollY, 0, computeMaxScrollX(),
+                    0, computeMaxScrollY())) {
+                invalidate();
+            }
             return;
         }
         float currentVelocity = mScroller.getCurrVelocity();
@@ -5869,13 +6084,37 @@
                     + " maxX=" + maxX + " maxY=" + maxY
                     + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY);
         }
+
+        // Allow sloppy flings without overscrolling at the edges.
+        if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) {
+            vx = 0;
+        }
+        if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) {
+            vy = 0;
+        }
+
+        if (mOverscrollDistance < mOverflingDistance) {
+            if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) {
+                vx = 0;
+            }
+            if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) {
+                vy = 0;
+            }
+        }
+
         mLastVelX = vx;
         mLastVelY = vy;
         mLastVelocity = velocity;
 
-        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY);
+        // no horizontal overscroll if the content just fits
+        mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY,
+                maxX == 0 ? 0 : mOverflingDistance, mOverflingDistance);
+        // Duration is calculated based on velocity. With range boundaries and overscroll
+        // we may not know how long the final animation will take. (Hence the deprecation
+        // warning on the call below.) It's not a big deal for scroll bars but if webcore
+        // resumes during this effect we will take a performance hit. See computeScroll;
+        // we resume webcore there when the animation is finished.
         final int time = mScroller.getDuration();
-        mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time);
         awakenScrollBars(time);
         invalidate();
     }
@@ -6674,6 +6913,10 @@
                             case MotionEvent.ACTION_CANCEL:
                                 if (mDeferTouchMode == TOUCH_DRAG_MODE) {
                                     // no fling in defer process
+                                    mScroller.springBack(mScrollX, mScrollY, 0,
+                                            computeMaxScrollX(), 0,
+                                            computeMaxScrollY());
+                                    invalidate();
                                     WebViewCore.resumePriority();
                                     WebViewCore.resumeUpdatePicture(mWebViewCore);
                                 }
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 70cfee9..423a788 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Rect;
@@ -39,6 +40,7 @@
 import android.util.SparseBooleanArray;
 import android.util.StateSet;
 import android.view.ActionMode;
+import android.view.ContextMenu.ContextMenuInfo;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -52,7 +54,6 @@
 import android.view.ViewDebug;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
-import android.view.ContextMenu.ContextMenuInfo;
 import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -138,6 +139,17 @@
     static final int TOUCH_MODE_FLING = 4;
 
     /**
+     * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
+     */
+    static final int TOUCH_MODE_OVERSCROLL = 5;
+
+    /**
+     * Indicates the view is being flung outside of normal content bounds
+     * and will spring back.
+     */
+    static final int TOUCH_MODE_OVERFLING = 6;
+
+    /**
      * Regular layout - usually an unsolicited layout from the view system
      */
     static final int LAYOUT_NORMAL = 0;
@@ -446,6 +458,16 @@
     private ContextMenuInfo mContextMenuInfo = null;
     
     /**
+     * Maximum distance to record overscroll
+     */
+    int mOverscrollMax;
+
+    /**
+     * Content height divided by this is the overscroll limit.
+     */
+    static final int OVERSCROLL_LIMIT_DIVISOR = 3;
+
+    /**
      * Used to request a layout when we changed touch mode
      */
     private static final int TOUCH_MODE_UNKNOWN = -1;
@@ -548,6 +570,48 @@
     private static final int INVALID_POINTER = -1;
 
     /**
+     * Maximum distance to overscroll by during edge effects
+     */
+    int mOverscrollDistance;
+
+    /**
+     * Maximum distance to overfling during edge effects
+     */
+    int mOverflingDistance;
+
+    // These two EdgeGlows are always set and used together.
+    // Checking one for null is as good as checking both.
+
+    /**
+     * Tracks the state of the top edge glow.
+     */
+    private EdgeGlow mEdgeGlowTop;
+
+    /**
+     * Tracks the state of the bottom edge glow.
+     */
+    private EdgeGlow mEdgeGlowBottom;
+
+    /**
+     * An estimate of how many pixels are between the top of the list and
+     * the top of the first position in the adapter, based on the last time
+     * we saw it. Used to hint where to draw edge glows.
+     */
+    private int mFirstPositionDistanceGuess;
+
+    /**
+     * An estimate of how many pixels are between the bottom of the list and
+     * the bottom of the last position in the adapter, based on the last time
+     * we saw it. Used to hint where to draw edge glows.
+     */
+    private int mLastPositionDistanceGuess;
+
+    /**
+     * Used for determining when to cancel out of overscroll.
+     */
+    private int mDirection = 0;
+
+    /**
      * Interface definition for a callback to be invoked when the list or grid
      * has been scrolled.
      */
@@ -690,9 +754,29 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mOverscrollDistance = configuration.getScaledOverscrollDistance();
+        mOverflingDistance = configuration.getScaledOverflingDistance();
+
         mDensityScale = getContext().getResources().getDisplayMetrics().density;
     }
 
+    @Override
+    public void setOverScrollMode(int mode) {
+        if (mode != OVER_SCROLL_NEVER) {
+            if (mEdgeGlowTop == null) {
+                final Resources res = getContext().getResources();
+                final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+                final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+                mEdgeGlowTop = new EdgeGlow(edge, glow);
+                mEdgeGlowBottom = new EdgeGlow(edge, glow);
+            }
+        } else {
+            mEdgeGlowTop = null;
+            mEdgeGlowBottom = null;
+        }
+        super.setOverScrollMode(mode);
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -1003,6 +1087,18 @@
     }
 
     /**
+     * @return true if all list content currently fits within the view boundaries
+     */
+    private boolean contentFits() {
+        final int childCount = getChildCount();
+        if (childCount != mItemCount) {
+            return false;
+        }
+
+        return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= mBottom;
+    }
+
+    /**
      * Enables fast scrolling by letting the user quickly scroll through lists by
      * dragging the fast scroll thumb. The adapter attached to the list may want
      * to implement {@link SectionIndexer} if it wishes to display alphabet preview and
@@ -1540,6 +1636,10 @@
         int result;
         if (mSmoothScrollbarEnabled) {
             result = Math.max(mItemCount * 100, 0);
+            if (mScrollY != 0) {
+                // Compensate for overscroll
+                result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100));
+            }
         } else {
             result = mItemCount;
         }
@@ -1612,6 +1712,8 @@
 
         layoutChildren();
         mInLayout = false;
+
+        mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR;
     }
 
     /**
@@ -2126,6 +2228,7 @@
                 mFlingRunnable.endFling();
                 if (mScrollY != 0) {
                     mScrollY = 0;
+                    finishGlows();
                     invalidate();
                 }
             }
@@ -2445,9 +2548,10 @@
         // Check if we have moved far enough that it looks more like a
         // scroll than a tap
         final int distance = Math.abs(deltaY);
-        if (distance > mTouchSlop) {
+        final boolean overscroll = mScrollY != 0;
+        if (overscroll || distance > mTouchSlop) {
             createScrollingCache();
-            mTouchMode = TOUCH_MODE_SCROLL;
+            mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
             mMotionCorrection = deltaY;
             final Handler handler = getHandler();
             // Handler should not be null unless the AbsListView is not attached to a
@@ -2483,6 +2587,19 @@
                 // touch mode). Force an initial layout to get rid of the selection.
                 layoutChildren();
             }
+        } else {
+            int touchMode = mTouchMode;
+            if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
+                if (mFlingRunnable != null) {
+                    mFlingRunnable.endFling();
+                }
+
+                if (mScrollY != 0) {
+                    mScrollY = 0;
+                    finishGlows();
+                    invalidate();
+                }
+            }
         }
     }
 
@@ -2513,49 +2630,63 @@
 
         switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN: {
-            mActivePointerId = ev.getPointerId(0);
-            final int x = (int) ev.getX();
-            final int y = (int) ev.getY();
-            int motionPosition = pointToPosition(x, y);
-            if (!mDataChanged) {
-                if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
-                        && (getAdapter().isEnabled(motionPosition))) {
-                    // User clicked on an actual view (and was not stopping a fling). It might be a
-                    // click or a scroll. Assume it is a click until proven otherwise
-                    mTouchMode = TOUCH_MODE_DOWN;
-                    // FIXME Debounce
-                    if (mPendingCheckForTap == null) {
-                        mPendingCheckForTap = new CheckForTap();
-                    }
-                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
-                } else {
-                    if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
-                        // If we couldn't find a view to click on, but the down event was touching
-                        // the edge, we will bail out and try again. This allows the edge correcting
-                        // code in ViewRoot to try to find a nearby view to select
-                        return false;
-                    }
+            switch (mTouchMode) {
+            case TOUCH_MODE_OVERFLING: {
+                mFlingRunnable.endFling();
+                mTouchMode = TOUCH_MODE_OVERSCROLL;
+                mMotionY = mLastY = (int) ev.getY();
+                mMotionCorrection = 0;
+                mActivePointerId = ev.getPointerId(0);
+                break;
+            }
 
-                    if (mTouchMode == TOUCH_MODE_FLING) {
-                        // Stopped a fling. It is a scroll.
-                        createScrollingCache();
-                        mTouchMode = TOUCH_MODE_SCROLL;
-                        mMotionCorrection = 0;
-                        motionPosition = findMotionRow(y);
-                        mFlingRunnable.flywheelTouch();
+            default: {
+                mActivePointerId = ev.getPointerId(0);
+                final int x = (int) ev.getX();
+                final int y = (int) ev.getY();
+                int motionPosition = pointToPosition(x, y);
+                if (!mDataChanged) {
+                    if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
+                            && (getAdapter().isEnabled(motionPosition))) {
+                        // User clicked on an actual view (and was not stopping a fling). It might be a
+                        // click or a scroll. Assume it is a click until proven otherwise
+                        mTouchMode = TOUCH_MODE_DOWN;
+                        // FIXME Debounce
+                        if (mPendingCheckForTap == null) {
+                            mPendingCheckForTap = new CheckForTap();
+                        }
+                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
+                    } else {
+                        if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
+                            // If we couldn't find a view to click on, but the down event was touching
+                            // the edge, we will bail out and try again. This allows the edge correcting
+                            // code in ViewRoot to try to find a nearby view to select
+                            return false;
+                        }
+
+                        if (mTouchMode == TOUCH_MODE_FLING) {
+                            // Stopped a fling. It is a scroll.
+                            createScrollingCache();
+                            mTouchMode = TOUCH_MODE_SCROLL;
+                            mMotionCorrection = 0;
+                            motionPosition = findMotionRow(y);
+                            mFlingRunnable.flywheelTouch();
+                        }
                     }
                 }
-            }
 
-            if (motionPosition >= 0) {
-                // Remember where the motion event started
-                v = getChildAt(motionPosition - mFirstPosition);
-                mMotionViewOriginalTop = v.getTop();
+                if (motionPosition >= 0) {
+                    // Remember where the motion event started
+                    v = getChildAt(motionPosition - mFirstPosition);
+                    mMotionViewOriginalTop = v.getTop();
+                }
+                mMotionX = x;
+                mMotionY = y;
+                mMotionPosition = motionPosition;
+                mLastY = Integer.MIN_VALUE;
+                break;
             }
-            mMotionX = x;
-            mMotionY = y;
-            mMotionPosition = motionPosition;
-            mLastY = Integer.MIN_VALUE;
+            }
             break;
         }
 
@@ -2593,9 +2724,25 @@
                         requestDisallowInterceptTouchEvent(true);
                     }
 
+                    final int rawDeltaY = deltaY;
                     deltaY -= mMotionCorrection;
                     int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
 
+                    final int motionIndex;
+                    if (mMotionPosition >= 0) {
+                        motionIndex = mMotionPosition - mFirstPosition;
+                    } else {
+                        // If we don't have a motion position that we can reliably track,
+                        // pick something in the middle to make a best guess at things below.
+                        motionIndex = getChildCount() / 2;
+                    }
+
+                    int motionViewPrevTop = 0;
+                    View motionView = this.getChildAt(motionIndex);
+                    if (motionView != null) {
+                        motionViewPrevTop = motionView.getTop();
+                    }
+
                     // No need to do all this work if we're not going to move anyway
                     boolean atEdge = false;
                     if (incrementalDeltaY != 0) {
@@ -2603,23 +2750,117 @@
                     }
 
                     // Check to see if we have bumped into the scroll limit
-                    if (atEdge && getChildCount() > 0) {
-                        // Treat this like we're starting a new scroll from the current
-                        // position. This will let the user start scrolling back into
-                        // content immediately rather than needing to scroll back to the
-                        // point where they hit the limit first.
-                        int motionPosition = findMotionRow(y);
-                        if (motionPosition >= 0) {
-                            final View motionView = getChildAt(motionPosition - mFirstPosition);
-                            mMotionViewOriginalTop = motionView.getTop();
+                    motionView = this.getChildAt(motionIndex);
+                    if (motionView != null) {
+                        // Check if the top of the motion view is where it is
+                        // supposed to be
+                        final int motionViewRealTop = motionView.getTop();
+                        if (atEdge) {
+                            // Apply overscroll
+
+                            int overscroll = -incrementalDeltaY -
+                                    (motionViewRealTop - motionViewPrevTop);
+                            overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
+                                    0, mOverscrollDistance, true);
+                            if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
+                                // Don't allow overfling if we're at the edge.
+                                mVelocityTracker.clear();
+                            }
+
+                            final int overscrollMode = getOverScrollMode();
+                            if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                                    (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                                            !contentFits())) {
+                                mDirection = 0; // Reset when entering overscroll.
+                                mTouchMode = TOUCH_MODE_OVERSCROLL;
+                                if (rawDeltaY > 0) {
+                                    mEdgeGlowTop.onPull((float) overscroll / getHeight());
+                                    if (!mEdgeGlowBottom.isFinished()) {
+                                        mEdgeGlowBottom.onRelease();
+                                    }
+                                } else if (rawDeltaY < 0) {
+                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight());
+                                    if (!mEdgeGlowTop.isFinished()) {
+                                        mEdgeGlowTop.onRelease();
+                                    }
+                                }
+                            }
                         }
                         mMotionY = y;
-                        mMotionPosition = motionPosition;
                         invalidate();
                     }
                     mLastY = y;
                 }
                 break;
+
+            case TOUCH_MODE_OVERSCROLL:
+                if (y != mLastY) {
+                    final int rawDeltaY = deltaY;
+                    deltaY -= mMotionCorrection;
+                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+
+                    final int oldScroll = mScrollY;
+                    final int newScroll = oldScroll - incrementalDeltaY;
+                    int newDirection = y > mLastY ? 1 : -1;
+
+                    if (mDirection == 0) {
+                        mDirection = newDirection;
+                    }
+
+                    if (mDirection != newDirection) {
+                        // Coming back to 'real' list scrolling
+                        incrementalDeltaY = -newScroll;
+                        mScrollY = 0;
+
+                        // No need to do all this work if we're not going to move anyway
+                        if (incrementalDeltaY != 0) {
+                            trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
+                        }
+
+                        // Check to see if we are back in
+                        View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+                        if (motionView != null) {
+                            mTouchMode = TOUCH_MODE_SCROLL;
+
+                            // We did not scroll the full amount. Treat this essentially like the
+                            // start of a new touch scroll
+                            final int motionPosition = findClosestMotionRow(y);
+
+                            mMotionCorrection = 0;
+                            motionView = getChildAt(motionPosition - mFirstPosition);
+                            mMotionViewOriginalTop = motionView.getTop();
+                            mMotionY = y;
+                            mMotionPosition = motionPosition;
+                        }
+                    } else {
+                        overScrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0,
+                                0, mOverscrollDistance, true);
+                        final int overscrollMode = getOverScrollMode();
+                        if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                                        !contentFits())) {
+                            if (rawDeltaY > 0) {
+                                mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight());
+                                if (!mEdgeGlowBottom.isFinished()) {
+                                    mEdgeGlowBottom.onRelease();
+                                }
+                            } else if (rawDeltaY < 0) {
+                                mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight());
+                                if (!mEdgeGlowTop.isFinished()) {
+                                    mEdgeGlowTop.onRelease();
+                                }
+                            }
+                            invalidate();
+                        }
+                        if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
+                            // Don't allow overfling if we're at the edge.
+                            mVelocityTracker.clear();
+                        }
+                    }
+                    mLastY = y;
+                    mDirection = newDirection;
+                }
+                break;
             }
 
             break;
@@ -2693,19 +2934,30 @@
             case TOUCH_MODE_SCROLL:
                 final int childCount = getChildCount();
                 if (childCount > 0) {
-                    if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top &&
+                    final int firstChildTop = getChildAt(0).getTop();
+                    final int lastChildBottom = getChildAt(childCount - 1).getBottom();
+                    final int contentTop = mListPadding.top;
+                    final int contentBottom = getHeight() - mListPadding.bottom;
+                    if (mFirstPosition == 0 && firstChildTop >= contentTop &&
                             mFirstPosition + childCount < mItemCount &&
-                            getChildAt(childCount - 1).getBottom() <=
-                                    getHeight() - mListPadding.bottom) {
+                            lastChildBottom <= getHeight() - contentBottom) {
                         mTouchMode = TOUCH_MODE_REST;
                         reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                     } else {
                         final VelocityTracker velocityTracker = mVelocityTracker;
                         velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+
                         final int initialVelocity = (int)
                                 (velocityTracker.getYVelocity(mActivePointerId) * mVelocityScale);
-    
-                        if (Math.abs(initialVelocity) > mMinimumVelocity) {
+                        // Fling if we have enough velocity and we aren't at a boundary.
+                        // Since we can potentially overfling more than we can overscroll, don't
+                        // allow the weird behavior where you can scroll to a boundary then
+                        // fling further.
+                        if (Math.abs(initialVelocity) > mMinimumVelocity &&
+                                !((mFirstPosition == 0 &&
+                                        firstChildTop == contentTop - mOverscrollDistance) ||
+                                  (mFirstPosition + childCount == mItemCount &&
+                                        lastChildBottom == contentBottom + mOverscrollDistance))) {
                             if (mFlingRunnable == null) {
                                 mFlingRunnable = new FlingRunnable();
                             }
@@ -2725,10 +2977,32 @@
                     reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
                 }
                 break;
+
+            case TOUCH_MODE_OVERSCROLL:
+                if (mFlingRunnable == null) {
+                    mFlingRunnable = new FlingRunnable();
+                }
+                final VelocityTracker velocityTracker = mVelocityTracker;
+                velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+                final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
+
+                reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
+                if (Math.abs(initialVelocity) > mMinimumVelocity) {
+                    mFlingRunnable.startOverfling(-initialVelocity);
+                } else {
+                    mFlingRunnable.startSpringback();
+                }
+
+                break;
             }
 
             setPressed(false);
 
+            if (mEdgeGlowTop != null) {
+                mEdgeGlowTop.onRelease();
+                mEdgeGlowBottom.onRelease();
+            }
+
             // Need to redraw since we probably aren't drawing the selector anymore
             invalidate();
 
@@ -2759,24 +3033,42 @@
         }
 
         case MotionEvent.ACTION_CANCEL: {
-            mTouchMode = TOUCH_MODE_REST;
-            setPressed(false);
-            View motionView = getChildAt(mMotionPosition - mFirstPosition);
-            if (motionView != null) {
-                motionView.setPressed(false);
-            }
-            clearScrollingCache();
+            switch (mTouchMode) {
+            case TOUCH_MODE_OVERSCROLL:
+                if (mFlingRunnable == null) {
+                    mFlingRunnable = new FlingRunnable();
+                }
+                mFlingRunnable.startSpringback();
+                break;
 
-            final Handler handler = getHandler();
-            if (handler != null) {
-                handler.removeCallbacks(mPendingCheckForLongPress);
-            }
+            case TOUCH_MODE_OVERFLING:
+                // Do nothing - let it play out.
+                break;
 
-            if (mVelocityTracker != null) {
-                mVelocityTracker.recycle();
-                mVelocityTracker = null;
+            default:
+                mTouchMode = TOUCH_MODE_REST;
+                setPressed(false);
+                View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
+                if (motionView != null) {
+                    motionView.setPressed(false);
+                }
+                clearScrollingCache();
+
+                final Handler handler = getHandler();
+                if (handler != null) {
+                    handler.removeCallbacks(mPendingCheckForLongPress);
+                }
+
+                if (mVelocityTracker != null) {
+                    mVelocityTracker.recycle();
+                    mVelocityTracker = null;
+                }
             }
             
+            if (mEdgeGlowTop != null) {
+                mEdgeGlowTop.onRelease();
+                mEdgeGlowBottom.onRelease();
+            }
             mActivePointerId = INVALID_POINTER;
             break;
         }
@@ -2801,10 +3093,61 @@
     }
 
     @Override
+    protected void onOverScrolled(int scrollX, int scrollY,
+            boolean clampedX, boolean clampedY) {
+        mScrollY = scrollY;
+
+        if (clampedY) {
+            // Velocity is broken by hitting the limit; don't start a fling off of this.
+            if (mVelocityTracker != null) {
+                mVelocityTracker.clear();
+            }
+        }
+        awakenScrollBars();
+    }
+
+    @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
+        if (mEdgeGlowTop != null) {
+            final int scrollY = mScrollY;
+            if (!mEdgeGlowTop.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth();
+
+                canvas.translate(0, Math.min(0, scrollY + mFirstPositionDistanceGuess));
+                mEdgeGlowTop.setSize(width, getHeight());
+                if (mEdgeGlowTop.draw(canvas)) {
+                    invalidate();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+            if (!mEdgeGlowBottom.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth();
+                final int height = getHeight();
+
+                canvas.translate(-width, 0);
+                canvas.rotate(-180, width, 0);
+                canvas.translate(0, -height);
+                mEdgeGlowBottom.setSize(width, height);
+                if (mEdgeGlowBottom.draw(canvas)) {
+                    invalidate();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+        }
         if (mFastScroller != null) {
-            mFastScroller.draw(canvas);
+            final int scrollY = mScrollY;
+            if (scrollY != 0) {
+                // Pin to the top/bottom during overscroll
+                int restoreCount = canvas.save();
+                canvas.translate(0, (float) scrollY);
+                mFastScroller.draw(canvas);
+                canvas.restoreToCount(restoreCount);
+            } else {
+                mFastScroller.draw(canvas);
+            }
         }
     }
 
@@ -2823,6 +3166,10 @@
         switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_DOWN: {
             int touchMode = mTouchMode;
+            if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
+                mMotionCorrection = 0;
+                return true;
+            }
             
             final int x = (int) ev.getX();
             final int y = (int) ev.getY();
@@ -2887,6 +3234,7 @@
             final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
             mMotionX = (int) ev.getX(newPointerIndex);
             mMotionY = (int) ev.getY(newPointerIndex);
+            mMotionCorrection = 0;
             mActivePointerId = ev.getPointerId(newPointerIndex);
             if (mVelocityTracker != null) {
                 mVelocityTracker.clear();
@@ -2942,7 +3290,7 @@
         /**
          * Tracks the decay of a fling scroll
          */
-        private final Scroller mScroller;
+        private final OverScroller mScroller;
 
         /**
          * Y value reported by mScroller on the previous fling
@@ -2953,7 +3301,7 @@
             public void run() {
                 final int activeId = mActivePointerId;
                 final VelocityTracker vt = mVelocityTracker;
-                final Scroller scroller = mScroller;
+                final OverScroller scroller = mScroller;
                 if (vt == null || activeId == INVALID_POINTER) {
                     return;
                 }
@@ -2975,7 +3323,7 @@
         private static final int FLYWHEEL_TIMEOUT = 40; // milliseconds
 
         FlingRunnable() {
-            mScroller = new Scroller(getContext());
+            mScroller = new OverScroller(getContext());
         }
 
         void start(int initialVelocity) {
@@ -2998,6 +3346,42 @@
             }
         }
 
+        void startSpringback() {
+            if (mScroller.springBack(0, mScrollY, 0, 0, 0, 0)) {
+                mTouchMode = TOUCH_MODE_OVERFLING;
+                invalidate();
+                post(this);
+            } else {
+                mTouchMode = TOUCH_MODE_REST;
+            }
+        }
+
+        void startOverfling(int initialVelocity) {
+            final int min = mScrollY > 0 ? Integer.MIN_VALUE : 0;
+            final int max = mScrollY > 0 ? 0 : Integer.MAX_VALUE;
+            mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, min, max, 0, getHeight());
+            mTouchMode = TOUCH_MODE_OVERFLING;
+            invalidate();
+            post(this);
+        }
+
+        void edgeReached(int delta) {
+            mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance);
+            final int overscrollMode = getOverScrollMode();
+            if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                    (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && !contentFits())) {
+                mTouchMode = TOUCH_MODE_OVERFLING;
+                final int vel = (int) mScroller.getCurrVelocity();
+                if (delta > 0) {
+                    mEdgeGlowTop.onAbsorb(vel);
+                } else {
+                    mEdgeGlowBottom.onAbsorb(vel);
+                }
+            }
+            invalidate();
+            post(this);
+        }
+
         void startScroll(int distance, int duration) {
             int initialY = distance < 0 ? Integer.MAX_VALUE : 0;
             mLastFlingY = initialY;
@@ -3040,58 +3424,100 @@
                     return;
                 }
                 // Fall through
-            case TOUCH_MODE_FLING:
+            case TOUCH_MODE_FLING: {
                 if (mItemCount == 0 || getChildCount() == 0) {
                     endFling();
                     return;
                 }
-                break;
-            }
-            final Scroller scroller = mScroller;
-            boolean more = scroller.computeScrollOffset();
-            final int y = scroller.getCurrY();
 
-            // Flip sign to convert finger direction to list items direction
-            // (e.g. finger moving down means list is moving towards the top)
-            int delta = mLastFlingY - y;
+                final OverScroller scroller = mScroller;
+                boolean more = scroller.computeScrollOffset();
+                final int y = scroller.getCurrY();
 
-            // Pretend that each frame of a fling scroll is a touch scroll
-            if (delta > 0) {
-                // List is moving towards the top. Use first view as mMotionPosition
-                mMotionPosition = mFirstPosition;
-                final View firstView = getChildAt(0);
-                mMotionViewOriginalTop = firstView.getTop();
+                // Flip sign to convert finger direction to list items direction
+                // (e.g. finger moving down means list is moving towards the top)
+                int delta = mLastFlingY - y;
 
-                // Don't fling more than 1 screen
-                delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
-            } else {
-                // List is moving towards the bottom. Use last view as mMotionPosition
-                int offsetToLast = getChildCount() - 1;
-                mMotionPosition = mFirstPosition + offsetToLast;
+                // Pretend that each frame of a fling scroll is a touch scroll
+                if (delta > 0) {
+                    // List is moving towards the top. Use first view as mMotionPosition
+                    mMotionPosition = mFirstPosition;
+                    final View firstView = getChildAt(0);
+                    mMotionViewOriginalTop = firstView.getTop();
 
-                final View lastView = getChildAt(offsetToLast);
-                mMotionViewOriginalTop = lastView.getTop();
+                    // Don't fling more than 1 screen
+                    delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
+                } else {
+                    // List is moving towards the bottom. Use last view as mMotionPosition
+                    int offsetToLast = getChildCount() - 1;
+                    mMotionPosition = mFirstPosition + offsetToLast;
 
-                // Don't fling more than 1 screen
-                delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
-            }
+                    final View lastView = getChildAt(offsetToLast);
+                    mMotionViewOriginalTop = lastView.getTop();
 
-            // Don't stop just because delta is zero (it could have been rounded)
-            final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
+                    // Don't fling more than 1 screen
+                    delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
+                }
 
-            if (more && !atEnd) {
-                invalidate();
-                mLastFlingY = y;
-                post(this);
-            } else {
-                endFling();
+                // Check to see if we have bumped into the scroll limit
+                View motionView = getChildAt(mMotionPosition - mFirstPosition);
+                int oldTop = 0;
+                if (motionView != null) {
+                    oldTop = motionView.getTop();
+                }
 
-                if (PROFILE_FLINGING) {
-                    if (mFlingProfilingStarted) {
-                        Debug.stopMethodTracing();
-                        mFlingProfilingStarted = false;
+                // Don't stop just because delta is zero (it could have been rounded)
+                final boolean atEnd = trackMotionScroll(delta, delta) && (delta != 0);
+                if (atEnd) {
+                    if (motionView != null) {
+                        // Tweak the scroll for how far we overshot
+                        int overshoot = -(delta - (motionView.getTop() - oldTop));
+                        overScrollBy(0, overshoot, 0, mScrollY, 0, 0,
+                                0, mOverflingDistance, false);
+                    }
+                    edgeReached(delta);
+                    break;
+                }
+
+                if (more && !atEnd) {
+                    invalidate();
+                    mLastFlingY = y;
+                    post(this);
+                } else {
+                    endFling();
+
+                    if (PROFILE_FLINGING) {
+                        if (mFlingProfilingStarted) {
+                            Debug.stopMethodTracing();
+                            mFlingProfilingStarted = false;
+                        }
+
+                        if (mFlingStrictSpan != null) {
+                            mFlingStrictSpan.finish();
+                            mFlingStrictSpan = null;
+                        }
                     }
                 }
+                break;
+            }
+
+            case TOUCH_MODE_OVERFLING: {
+                final OverScroller scroller = mScroller;
+                if (scroller.computeScrollOffset()) {
+                    final int scrollY = mScrollY;
+                    final int deltaY = scroller.getCurrY() - scrollY;
+                    if (overScrollBy(0, deltaY, 0, scrollY, 0, 0,
+                            0, mOverflingDistance, false)) {
+                        startSpringback();
+                    } else {
+                        invalidate();
+                        post(this);
+                    }
+                } else {
+                    endFling();
+                }
+                break;
+            }
             }
         }
     }
@@ -3620,16 +4046,29 @@
 
         final int firstPosition = mFirstPosition;
 
-        if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) {
-            // Don't need to move views down if the top of the first position
-            // is already visible
-            return true;
+        // Update our guesses for where the first and last views are
+        if (firstPosition == 0) {
+            mFirstPositionDistanceGuess = firstTop - mListPadding.top;
+        } else {
+            mFirstPositionDistanceGuess += incrementalDeltaY;
+        }
+        if (firstPosition + childCount == mItemCount) {
+            mLastPositionDistanceGuess = lastBottom + mListPadding.bottom;
+        } else {
+            mLastPositionDistanceGuess += incrementalDeltaY;
         }
 
-        if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) {
+        if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) {
+            // Don't need to move views down if the top of the first position
+            // is already visible
+            return incrementalDeltaY != 0;
+        }
+
+        if (firstPosition + childCount == mItemCount && lastBottom <= end &&
+                incrementalDeltaY <= 0) {
             // Don't need to move views up if the bottom of the last position
             // is already visible
-            return true;
+            return incrementalDeltaY != 0;
         }
 
         final boolean down = incrementalDeltaY < 0;
@@ -3805,6 +4244,22 @@
     abstract int findMotionRow(int y);
 
     /**
+     * Find the row closest to y. This row will be used as the motion row when scrolling.
+     *
+     * @param y Where the user touched
+     * @return The position of the first (or only) item in the row closest to y
+     */
+    int findClosestMotionRow(int y) {
+        final int childCount = getChildCount();
+        if (childCount == 0) {
+            return INVALID_POSITION;
+        }
+
+        final int motionRow = findMotionRow(y);
+        return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
+    }
+
+    /**
      * Causes all the views to be rebuilt and redrawn.
      */
     public void invalidateViews() {
@@ -4577,6 +5032,13 @@
         return result;
     }
 
+    private void finishGlows() {
+        if (mEdgeGlowTop != null) {
+            mEdgeGlowTop.finish();
+            mEdgeGlowBottom.finish();
+        }
+    }
+
     /**
      * Sets up this AbsListView to use a remote views adapter which connects to a RemoteViewsService
      * through the specified intent.
diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java
new file mode 100644
index 0000000..9b3a6e6
--- /dev/null
+++ b/core/java/android/widget/EdgeGlow.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.view.animation.AnimationUtils;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+
+/**
+ * This class performs the glow effect used at the edges of scrollable widgets.
+ * @hide
+ */
+public class EdgeGlow {
+    private static final String TAG = "EdgeGlow";
+
+    // Time it will take the effect to fully recede in ms
+    private static final int RECEDE_TIME = 1000;
+
+    // Time it will take before a pulled glow begins receding
+    private static final int PULL_TIME = 167;
+
+    // Time it will take for a pulled glow to decay to partial strength before release
+    private static final int PULL_DECAY_TIME = 1000;
+
+    private static final float MAX_ALPHA = 0.8f;
+    private static final float HELD_EDGE_ALPHA = 0.7f;
+    private static final float HELD_EDGE_SCALE_Y = 0.5f;
+    private static final float HELD_GLOW_ALPHA = 0.5f;
+    private static final float HELD_GLOW_SCALE_Y = 0.5f;
+
+    private static final float MAX_GLOW_HEIGHT = 3.f;
+
+    private static final float PULL_GLOW_BEGIN = 1.f;
+    private static final float PULL_EDGE_BEGIN = 0.6f;
+
+    // Minimum velocity that will be absorbed
+    private static final int MIN_VELOCITY = 100;
+
+    private static final float EPSILON = 0.001f;
+
+    private final Drawable mEdge;
+    private final Drawable mGlow;
+    private int mWidth;
+    private int mHeight;
+
+    private float mEdgeAlpha;
+    private float mEdgeScaleY;
+    private float mGlowAlpha;
+    private float mGlowScaleY;
+
+    private float mEdgeAlphaStart;
+    private float mEdgeAlphaFinish;
+    private float mEdgeScaleYStart;
+    private float mEdgeScaleYFinish;
+    private float mGlowAlphaStart;
+    private float mGlowAlphaFinish;
+    private float mGlowScaleYStart;
+    private float mGlowScaleYFinish;
+
+    private long mStartTime;
+    private float mDuration;
+
+    private final Interpolator mInterpolator;
+
+    private static final int STATE_IDLE = 0;
+    private static final int STATE_PULL = 1;
+    private static final int STATE_ABSORB = 2;
+    private static final int STATE_RECEDE = 3;
+    private static final int STATE_PULL_DECAY = 4;
+
+    // How much dragging should effect the height of the edge image.
+    // Number determined by user testing.
+    private static final int PULL_DISTANCE_EDGE_FACTOR = 5;
+
+    // How much dragging should effect the height of the glow image.
+    // Number determined by user testing.
+    private static final int PULL_DISTANCE_GLOW_FACTOR = 5;
+    private static final float PULL_DISTANCE_ALPHA_GLOW_FACTOR = 0.8f;
+
+    private static final int VELOCITY_EDGE_FACTOR = 8;
+    private static final int VELOCITY_GLOW_FACTOR = 16;
+
+    private int mState = STATE_IDLE;
+
+    private float mPullDistance;
+
+    public EdgeGlow(Drawable edge, Drawable glow) {
+        mEdge = edge;
+        mGlow = glow;
+
+        mInterpolator = new DecelerateInterpolator();
+    }
+
+    public void setSize(int width, int height) {
+        mWidth = width;
+        mHeight = height;
+    }
+
+    public boolean isFinished() {
+        return mState == STATE_IDLE;
+    }
+
+    public void finish() {
+        mState = STATE_IDLE;
+    }
+
+    /**
+     * Call when the object is pulled by the user.
+     *
+     * @param deltaDistance Change in distance since the last call
+     */
+    public void onPull(float deltaDistance) {
+        final long now = AnimationUtils.currentAnimationTimeMillis();
+        if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) {
+            return;
+        }
+        if (mState != STATE_PULL) {
+            mGlowScaleY = PULL_GLOW_BEGIN;
+        }
+        mState = STATE_PULL;
+
+        mStartTime = now;
+        mDuration = PULL_TIME;
+
+        mPullDistance += deltaDistance;
+        float distance = Math.abs(mPullDistance);
+
+        mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, MAX_ALPHA));
+        mEdgeScaleY = mEdgeScaleYStart = Math.max(
+                HELD_EDGE_SCALE_Y, Math.min(distance * PULL_DISTANCE_EDGE_FACTOR, 1.f));
+
+        mGlowAlpha = mGlowAlphaStart = Math.min(MAX_ALPHA,
+                mGlowAlpha +
+                (Math.abs(deltaDistance) * PULL_DISTANCE_ALPHA_GLOW_FACTOR));
+
+        float glowChange = Math.abs(deltaDistance);
+        if (deltaDistance > 0 && mPullDistance < 0) {
+            glowChange = -glowChange;
+        }
+        if (mPullDistance == 0) {
+            mGlowScaleY = 0;
+        }
+
+        // Do not allow glow to get larger than MAX_GLOW_HEIGHT.
+        mGlowScaleY = mGlowScaleYStart = Math.min(MAX_GLOW_HEIGHT, Math.max(
+                0, mGlowScaleY + glowChange * PULL_DISTANCE_GLOW_FACTOR));
+
+        mEdgeAlphaFinish = mEdgeAlpha;
+        mEdgeScaleYFinish = mEdgeScaleY;
+        mGlowAlphaFinish = mGlowAlpha;
+        mGlowScaleYFinish = mGlowScaleY;
+    }
+
+    /**
+     * Call when the object is released after being pulled.
+     */
+    public void onRelease() {
+        mPullDistance = 0;
+
+        if (mState != STATE_PULL && mState != STATE_PULL_DECAY) {
+            return;
+        }
+
+        mState = STATE_RECEDE;
+        mEdgeAlphaStart = mEdgeAlpha;
+        mEdgeScaleYStart = mEdgeScaleY;
+        mGlowAlphaStart = mGlowAlpha;
+        mGlowScaleYStart = mGlowScaleY;
+
+        mEdgeAlphaFinish = 0.f;
+        mEdgeScaleYFinish = 0.f;
+        mGlowAlphaFinish = 0.f;
+        mGlowScaleYFinish = 0.f;
+
+        mStartTime = AnimationUtils.currentAnimationTimeMillis();
+        mDuration = RECEDE_TIME;
+    }
+
+    /**
+     * Call when the effect absorbs an impact at the given velocity.
+     *
+     * @param velocity Velocity at impact in pixels per second.
+     */
+    public void onAbsorb(int velocity) {
+        mState = STATE_ABSORB;
+        velocity = Math.max(MIN_VELOCITY, Math.abs(velocity));
+
+        mStartTime = AnimationUtils.currentAnimationTimeMillis();
+        mDuration = 0.1f + (velocity * 0.03f);
+
+        // The edge should always be at least partially visible, regardless
+        // of velocity.
+        mEdgeAlphaStart = 0.f;
+        mEdgeScaleY = mEdgeScaleYStart = 0.f;
+        // The glow depends more on the velocity, and therefore starts out
+        // nearly invisible.
+        mGlowAlphaStart = 0.5f;
+        mGlowScaleYStart = 0.f;
+
+        // Factor the velocity by 8. Testing on device shows this works best to
+        // reflect the strength of the user's scrolling.
+        mEdgeAlphaFinish = Math.max(0, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1));
+        // Edge should never get larger than the size of its asset.
+        mEdgeScaleYFinish = Math.max(
+                HELD_EDGE_SCALE_Y, Math.min(velocity * VELOCITY_EDGE_FACTOR, 1.f));
+
+        // Growth for the size of the glow should be quadratic to properly
+        // respond
+        // to a user's scrolling speed. The faster the scrolling speed, the more
+        // intense the effect should be for both the size and the saturation.
+        mGlowScaleYFinish = Math.min(0.025f + (velocity * (velocity / 100) * 0.00015f), 1.75f);
+        // Alpha should change for the glow as well as size.
+        mGlowAlphaFinish = Math.max(
+                mGlowAlphaStart, Math.min(velocity * VELOCITY_GLOW_FACTOR * .00001f, MAX_ALPHA));
+    }
+
+
+    /**
+     * Draw into the provided canvas. Assumes that the canvas has been rotated
+     * accordingly and the size has been set. The effect will be drawn the full
+     * width of X=0 to X=width, emitting from Y=0 and extending to some factor <
+     * 1.f of height.
+     *
+     * @param canvas Canvas to draw into
+     * @return true if drawing should continue beyond this frame to continue the
+     *         animation
+     */
+    public boolean draw(Canvas canvas) {
+        update();
+
+        final int edgeHeight = mEdge.getIntrinsicHeight();
+        final int edgeWidth = mEdge.getIntrinsicWidth();
+        final int glowHeight = mGlow.getIntrinsicHeight();
+        final int glowWidth = mGlow.getIntrinsicWidth();
+
+        mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255));
+
+        // Center the glow inside the width of the container.
+        int glowLeft = (mWidth - glowWidth)/2;
+        mGlow.setBounds(glowLeft, 0, mWidth - glowLeft, (int) Math.min(
+                glowHeight * mGlowScaleY * glowHeight/ glowWidth * 0.6f,
+                glowHeight * MAX_GLOW_HEIGHT));
+        mGlow.draw(canvas);
+
+        mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255));
+
+        int edgeLeft = (mWidth - edgeWidth)/2;
+        mEdge.setBounds(edgeLeft, 0, mWidth - edgeLeft, (int) (edgeHeight * mEdgeScaleY));
+        mEdge.draw(canvas);
+
+        return mState != STATE_IDLE;
+    }
+
+    private void update() {
+        final long time = AnimationUtils.currentAnimationTimeMillis();
+        final float t = Math.min((time - mStartTime) / mDuration, 1.f);
+
+        final float interp = mInterpolator.getInterpolation(t);
+
+        mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp;
+        mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp;
+        mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp;
+        mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp;
+
+        if (t >= 1.f - EPSILON) {
+            switch (mState) {
+                case STATE_ABSORB:
+                    mState = STATE_RECEDE;
+                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
+                    mDuration = RECEDE_TIME;
+
+                    mEdgeAlphaStart = mEdgeAlpha;
+                    mEdgeScaleYStart = mEdgeScaleY;
+                    mGlowAlphaStart = mGlowAlpha;
+                    mGlowScaleYStart = mGlowScaleY;
+
+                    // After absorb, the glow and edge should fade to nothing.
+                    mEdgeAlphaFinish = 0.f;
+                    mEdgeScaleYFinish = 0.f;
+                    mGlowAlphaFinish = 0.f;
+                    mGlowScaleYFinish = 0.f;
+                    break;
+                case STATE_PULL:
+                    mState = STATE_PULL_DECAY;
+                    mStartTime = AnimationUtils.currentAnimationTimeMillis();
+                    mDuration = PULL_DECAY_TIME;
+
+                    mEdgeAlphaStart = mEdgeAlpha;
+                    mEdgeScaleYStart = mEdgeScaleY;
+                    mGlowAlphaStart = mGlowAlpha;
+                    mGlowScaleYStart = mGlowScaleY;
+
+                    // After pull, the glow and edge should fade to nothing.
+                    mEdgeAlphaFinish = 0.f;
+                    mEdgeScaleYFinish = 0.f;
+                    mGlowAlphaFinish = 0.f;
+                    mGlowScaleYFinish = 0.f;
+                    break;
+                case STATE_PULL_DECAY:
+                    // When receding, we want edge to decrease more slowly
+                    // than the glow.
+                    float factor = mGlowScaleYFinish != 0 ? 1
+                            / (mGlowScaleYFinish * mGlowScaleYFinish)
+                            : Float.MAX_VALUE;
+                    mEdgeScaleY = mEdgeScaleYStart +
+                        (mEdgeScaleYFinish - mEdgeScaleYStart) *
+                            interp * factor;
+                    break;
+                case STATE_RECEDE:
+                    mState = STATE_IDLE;
+                    break;
+            }
+        }
+    }
+}
diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java
index 114ae81..4146460 100644
--- a/core/java/android/widget/GridView.java
+++ b/core/java/android/widget/GridView.java
@@ -1954,7 +1954,12 @@
         // TODO: Account for vertical spacing too
         final int numColumns = mNumColumns;
         final int rowCount = (mItemCount + numColumns - 1) / numColumns;
-        return Math.max(rowCount * 100, 0);
+        int result = Math.max(rowCount * 100, 0);
+        if (mScrollY != 0) {
+            // Compensate for overscroll
+            result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100));
+        }
+        return result;
     }
 }
 
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index e30d4c8..9fc91da 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -16,19 +16,24 @@
 
 package android.widget;
 
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Rect;
+import com.android.internal.R;
+
 import android.util.AttributeSet;
-import android.view.FocusFinder;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.view.View;
+import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
 import android.view.ViewGroup;
+import android.view.KeyEvent;
+import android.view.FocusFinder;
+import android.view.MotionEvent;
 import android.view.ViewParent;
 import android.view.animation.AnimationUtils;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 
 import java.util.List;
 
@@ -65,7 +70,9 @@
     private long mLastScroll;
 
     private final Rect mTempRect = new Rect();
-    private Scroller mScroller;
+    private OverScroller mScroller;
+    private EdgeGlow mEdgeGlowLeft;
+    private EdgeGlow mEdgeGlowRight;
 
     /**
      * Flag to indicate that we are moving focus ourselves. This is so the
@@ -119,6 +126,9 @@
     private int mMinimumVelocity;
     private int mMaximumVelocity;
     
+    private int mOverscrollDistance;
+    private int mOverflingDistance;
+
     /**
      * ID of the active pointer. This is used to retain consistency during
      * drags/flings if multiple pointers are used.
@@ -191,7 +201,7 @@
 
 
     private void initScrollView() {
-        mScroller = new Scroller(getContext());
+        mScroller = new OverScroller(getContext());
         setFocusable(true);
         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
         setWillNotDraw(false);
@@ -199,6 +209,8 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mOverscrollDistance = configuration.getScaledOverscrollDistance();
+        mOverflingDistance = configuration.getScaledOverflingDistance();
     }
 
     @Override
@@ -463,6 +475,9 @@
                 /* Release the drag */
                 mIsBeingDragged = false;
                 mActivePointerId = INVALID_POINTER;
+                if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
+                    invalidate();
+                }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
                 onSecondaryPointerUp(ev);
@@ -495,9 +510,7 @@
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 final float x = ev.getX();
-                if (!(mIsBeingDragged = inChild((int) x, (int) ev.getY()))) {
-                    return false;
-                }
+                mIsBeingDragged = true;
 
                 /*
                  * If being flinged and user touches, stop the fling. isFinished
@@ -520,7 +533,36 @@
                     final int deltaX = (int) (mLastMotionX - x);
                     mLastMotionX = x;
 
-                    scrollBy(deltaX, 0);
+                    final int oldX = mScrollX;
+                    final int oldY = mScrollY;
+                    final int range = getScrollRange();
+                    if (overScrollBy(deltaX, 0, mScrollX, 0, range, 0,
+                            mOverscrollDistance, 0, true)) {
+                        // Break our velocity if we hit a scroll barrier.
+                        mVelocityTracker.clear();
+                    }
+                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+                    final int overscrollMode = getOverScrollMode();
+                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+                        final int pulledToX = oldX + deltaX;
+                        if (pulledToX < 0) {
+                            mEdgeGlowLeft.onPull((float) deltaX / getWidth());
+                            if (!mEdgeGlowRight.isFinished()) {
+                                mEdgeGlowRight.onRelease();
+                            }
+                        } else if (pulledToX > range) {
+                            mEdgeGlowRight.onPull((float) deltaX / getWidth());
+                            if (!mEdgeGlowLeft.isFinished()) {
+                                mEdgeGlowLeft.onRelease();
+                            }
+                        }
+                        if (mEdgeGlowLeft != null
+                                && (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) {
+                            invalidate();
+                        }
+                    }
                 }
                 break;
             case MotionEvent.ACTION_UP:
@@ -529,8 +571,15 @@
                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                     int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
 
-                    if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
-                        fling(-initialVelocity);
+                    if (getChildCount() > 0) {
+                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+                            fling(-initialVelocity);
+                        } else {
+                            final int right = getScrollRange();
+                            if (mScroller.springBack(mScrollX, mScrollY, 0, right, 0, 0)) {
+                                invalidate();
+                            }
+                        }
                     }
                     
                     mActivePointerId = INVALID_POINTER;
@@ -540,16 +589,27 @@
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                     }
+                    if (mEdgeGlowLeft != null) {
+                        mEdgeGlowLeft.onRelease();
+                        mEdgeGlowRight.onRelease();
+                    }
                 }
                 break;
             case MotionEvent.ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
+                    if (mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) {
+                        invalidate();
+                    }
                     mActivePointerId = INVALID_POINTER;
                     mIsBeingDragged = false;
                     if (mVelocityTracker != null) {
                         mVelocityTracker.recycle();
                         mVelocityTracker = null;
                     }
+                    if (mEdgeGlowLeft != null) {
+                        mEdgeGlowLeft.onRelease();
+                        mEdgeGlowRight.onRelease();
+                    }
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -576,12 +636,28 @@
         }
     }
     
+    @Override
+    protected void onOverScrolled(int scrollX, int scrollY,
+            boolean clampedX, boolean clampedY) {
+        // Treat animating scrolls differently; see #computeScroll() for why.
+        if (!mScroller.isFinished()) {
+            mScrollX = scrollX;
+            mScrollY = scrollY;
+            if (clampedX) {
+                mScroller.springBack(mScrollX, mScrollY, 0, getScrollRange(), 0, 0);
+            }
+        } else {
+            super.scrollTo(scrollX, scrollY);
+        }
+        awakenScrollBars();
+    }
+
     private int getScrollRange() {
         int scrollRange = 0;
         if (getChildCount() > 0) {
             View child = getChildAt(0);
             scrollRange = Math.max(0,
-                    child.getWidth() - getWidth() - mPaddingLeft - mPaddingRight);
+                    child.getWidth() - (getWidth() - mPaddingLeft - mPaddingRight));
         }
         return scrollRange;
     }
@@ -958,7 +1034,16 @@
             return contentWidth;
         }
         
-        return getChildAt(0).getRight();
+        int scrollRange = getChildAt(0).getRight();
+        final int scrollX = mScrollX;
+        final int overscrollRight = Math.max(0, scrollRange - contentWidth);
+        if (scrollX < 0) {
+            scrollRange -= scrollX;
+        } else if (scrollX > overscrollRight) {
+            scrollRange += scrollX - overscrollRight;
+        }
+
+        return scrollRange;
     }
     
     @Override
@@ -1019,14 +1104,20 @@
             int x = mScroller.getCurrX();
             int y = mScroller.getCurrY();
 
-            if (getChildCount() > 0) {
-                View child = getChildAt(0);
-                x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
-                y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
-                if (x != oldX || y != oldY) {
-                    mScrollX = x;
-                    mScrollY = y;
-                    onScrollChanged(x, y, oldX, oldY);
+            if (oldX != x || oldY != y) {
+                overScrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0,
+                        mOverflingDistance, 0, false);
+                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+                final int range = getScrollRange();
+                final int overscrollMode = getOverScrollMode();
+                if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                        (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+                    if (x < 0 && oldX >= 0) {
+                        mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity());
+                    } else if (x > range && oldX <= range) {
+                        mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity());
+                    }
                 }
             }
             awakenScrollBars();
@@ -1263,7 +1354,7 @@
             int right = getChildAt(0).getWidth();
     
             mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, 
-                    Math.max(0, right - width), 0, 0);
+                    Math.max(0, right - width), 0, 0, width/2, 0);
     
             final boolean movingRight = velocityX > 0;
     
@@ -1301,6 +1392,56 @@
         }
     }
 
+    @Override
+    public void setOverScrollMode(int mode) {
+        if (mode != OVER_SCROLL_NEVER) {
+            if (mEdgeGlowLeft == null) {
+                final Resources res = getContext().getResources();
+                final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+                final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+                mEdgeGlowLeft = new EdgeGlow(edge, glow);
+                mEdgeGlowRight = new EdgeGlow(edge, glow);
+            }
+        } else {
+            mEdgeGlowLeft = null;
+            mEdgeGlowRight = null;
+        }
+        super.setOverScrollMode(mode);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        if (mEdgeGlowLeft != null) {
+            final int scrollX = mScrollX;
+            if (!mEdgeGlowLeft.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int height = getHeight();
+
+                canvas.rotate(270);
+                canvas.translate(-height * 1.5f, Math.min(0, scrollX));
+                mEdgeGlowLeft.setSize(getHeight() * 2, getWidth());
+                if (mEdgeGlowLeft.draw(canvas)) {
+                    invalidate();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+            if (!mEdgeGlowRight.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth();
+                final int height = getHeight();
+
+                canvas.rotate(90);
+                canvas.translate(-height / 2, -(Math.max(getScrollRange(), scrollX) + width));
+                mEdgeGlowRight.setSize(height * 2, width);
+                if (mEdgeGlowRight.draw(canvas)) {
+                    invalidate();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+        }
+    }
+
     private int clamp(int n, int my, int child) {
         if (my >= child || n < 0) {
             return 0;
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 502cc38..fd4f950 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -104,6 +104,9 @@
     Drawable mDivider;
     int mDividerHeight;
     
+    Drawable mOverScrollHeader;
+    Drawable mOverScrollFooter;
+
     private boolean mIsCacheColorOpaque;
     private boolean mDividerIsOpaque;
 
@@ -152,6 +155,18 @@
             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(
                 com.android.internal.R.styleable.ListView_dividerHeight, 0);
@@ -2962,14 +2977,52 @@
         }
         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 (drawDividers) {
+        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;
@@ -2998,34 +3051,67 @@
 
             final int listBottom = mBottom - mTop - mListPadding.bottom + mScrollY;
             if (!mStackFromBottom) {
-                int bottom;
+                int bottom = 0;
                 
+                // 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++) {
                     if ((headerDividers || first + i >= headerCount) &&
                             (footerDividers || first + i < footerLimit)) {
                         View child = getChildAt(i);
                         bottom = child.getBottom();
                         // Don't draw dividers next to items that are not enabled
-                        if ((areAllItemsSelectable ||
-                                (adapter.isEnabled(first + i) && (i == count - 1 ||
-                                        adapter.isEnabled(first + i + 1))))) {
-                            bounds.top = bottom;
-                            bounds.bottom = bottom + dividerHeight;
-                            drawDivider(canvas, bounds, i);
-                        } else if (fillForMissingDividers) {
-                            bounds.top = bottom;
-                            bounds.bottom = bottom + dividerHeight;
-                            canvas.drawRect(bounds, paint);
+
+                        if (drawDividers &&
+                                (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) {
+                            if ((areAllItemsSelectable ||
+                                    (adapter.isEnabled(first + i) && (i == count - 1 ||
+                                            adapter.isEnabled(first + i + 1))))) {
+                                bounds.top = bottom;
+                                bounds.bottom = bottom + dividerHeight;
+                                drawDivider(canvas, bounds, i);
+                            } else if (fillForMissingDividers) {
+                                bounds.top = bottom;
+                                bounds.bottom = bottom + dividerHeight;
+                                canvas.drawRect(bounds, paint);
+                            }
                         }
                     }
                 }
+
+                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;
 
                 final int scrollY = mScrollY;
 
-                for (int i = 0; i < count; i++) {
+                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);
@@ -3052,9 +3138,16 @@
                 }
                 
                 if (count > 0 && scrollY > 0) {
-                    bounds.top = listBottom;
-                    bounds.bottom = listBottom + dividerHeight;
-                    drawDivider(canvas, bounds, -1);
+                    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);
+                    }
                 }
             }
         }
@@ -3150,6 +3243,45 @@
         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) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
new file mode 100644
index 0000000..cd81e31
--- /dev/null
+++ b/core/java/android/widget/OverScroller.java
@@ -0,0 +1,888 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget;
+
+import android.content.Context;
+import android.graphics.Interpolator;
+import android.view.ViewConfiguration;
+import android.view.animation.AnimationUtils;
+
+/**
+ * This class encapsulates scrolling with the ability to overshoot the bounds
+ * of a scrolling operation. This class is a drop-in replacement for
+ * {@link android.widget.Scroller} in most cases.
+ */
+public class OverScroller {
+    int mMode;
+
+    private final MagneticOverScroller mScrollerX;
+    private final MagneticOverScroller mScrollerY;
+
+    private float mDeceleration;
+    private final float mPpi;
+    private final boolean mFlywheel;
+
+    private static float DECELERATION_RATE = (float) (Math.log(0.75) / Math.log(0.9));
+    private static float ALPHA = 800; // pixels / seconds
+    private static float START_TENSION = 0.4f; // Tension at start: (0.4 * total T, 1.0 * Distance)
+    private static float END_TENSION = 1.0f - START_TENSION;
+    private static final int NB_SAMPLES = 100;
+    private static final float[] SPLINE_POSITION = new float[NB_SAMPLES + 1];
+    private static final float[] SPLINE_TIME = new float[NB_SAMPLES + 1];
+
+    private static final int DEFAULT_DURATION = 250;
+    private static final int SCROLL_MODE = 0;
+    private static final int FLING_MODE = 1;
+
+    static {
+        float x_min = 0.0f;
+        float y_min = 0.0f;
+        for (int i = 0; i < NB_SAMPLES; i++) {
+            final float alpha = (float) i / NB_SAMPLES;
+            {
+                float x_max = 1.0f;
+                float x, tx, coef;
+                while (true) {
+                    x = x_min + (x_max - x_min) / 2.0f;
+                    coef = 3.0f * x * (1.0f - x);
+                    tx = coef * ((1.0f - x) * START_TENSION + x * END_TENSION) + x * x * x;
+                    if (Math.abs(tx - alpha) < 1E-5) break;
+                    if (tx > alpha) x_max = x;
+                    else x_min = x;
+                }
+                SPLINE_POSITION[i] = coef + x * x * x;
+            }
+
+            {
+                float y_max = 1.0f;
+                float y, dy, coef;
+                while (true) {
+                    y = y_min + (y_max - y_min) / 2.0f;
+                    coef = 3.0f * y * (1.0f - y);
+                    dy = coef + y * y * y;
+                    if (Math.abs(dy - alpha) < 1E-5) break;
+                    if (dy > alpha) y_max = y;
+                    else y_min = y;
+                }
+                SPLINE_TIME[i] = coef * ((1.0f - y) * START_TENSION + y * END_TENSION) + y * y * y;
+            }
+        }
+        SPLINE_POSITION[NB_SAMPLES] = SPLINE_TIME[NB_SAMPLES] = 1.0f;
+    }
+
+    public OverScroller(Context context) {
+        this(context, null, 0.f, 0.f, true);
+    }
+
+    /**
+     * Creates an OverScroller.
+     * @param context The context of this application.
+     * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will
+     * be used.
+     * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the
+     * velocity which is preserved in the bounce when the horizontal edge is reached. A null value
+     * means no bounce.
+     * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction.
+     */
+    public OverScroller(Context context, Interpolator interpolator,
+            float bounceCoefficientX, float bounceCoefficientY, boolean flywheel) {
+        mFlywheel = flywheel;
+        mPpi = context.getResources().getDisplayMetrics().density * 160.0f;
+        mDeceleration = computeDeceleration(ViewConfiguration.getScrollFriction());
+        mScrollerX = new MagneticOverScroller();
+        mScrollerY = new MagneticOverScroller();
+
+        mScrollerX.setBounceCoefficient(bounceCoefficientX);
+        mScrollerY.setBounceCoefficient(bounceCoefficientY);
+    }
+
+
+    /**
+     * The amount of friction applied to flings. The default value
+     * is {@link ViewConfiguration#getScrollFriction}.
+     *
+     * @param friction A scalar dimension-less value representing the coefficient of
+     *         friction.
+     */
+    public final void setFriction(float friction) {
+        mDeceleration = computeDeceleration(friction);
+    }
+
+    private float computeDeceleration(float friction) {
+        return 9.81f   // g (m/s^2)
+        * 39.37f               // inch/meter
+        * mPpi                 // pixels per inch
+        * friction;
+    }
+
+    /**
+     *
+     * Returns whether the scroller has finished scrolling.
+     *
+     * @return True if the scroller has finished scrolling, false otherwise.
+     */
+    public final boolean isFinished() {
+        return mScrollerX.mFinished && mScrollerY.mFinished;
+    }
+
+    /**
+     * Force the finished field to a particular value. Contrary to
+     * {@link #abortAnimation()}, forcing the animation to finished
+     * does NOT cause the scroller to move to the final x and y
+     * position.
+     *
+     * @param finished The new finished value.
+     */
+    public final void forceFinished(boolean finished) {
+        mScrollerX.mFinished = mScrollerY.mFinished = finished;
+    }
+
+    /**
+     * Returns the current X offset in the scroll.
+     *
+     * @return The new X offset as an absolute distance from the origin.
+     */
+    public final int getCurrX() {
+        return mScrollerX.mCurrentPosition;
+    }
+
+    /**
+     * Returns the current Y offset in the scroll.
+     *
+     * @return The new Y offset as an absolute distance from the origin.
+     */
+    public final int getCurrY() {
+        return mScrollerY.mCurrentPosition;
+    }
+
+    /**
+     * @hide
+     * Returns the current velocity.
+     *
+     * @return The original velocity less the deceleration, norm of the X and Y velocity vector.
+     */
+    public float getCurrVelocity() {
+        float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity;
+        squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity;
+        return (float) Math.sqrt(squaredNorm);
+    }
+
+    /**
+     * Returns the start X offset in the scroll.
+     *
+     * @return The start X offset as an absolute distance from the origin.
+     */
+    public final int getStartX() {
+        return mScrollerX.mStart;
+    }
+
+    /**
+     * Returns the start Y offset in the scroll.
+     *
+     * @return The start Y offset as an absolute distance from the origin.
+     */
+    public final int getStartY() {
+        return mScrollerY.mStart;
+    }
+
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     *
+     * @return The final X offset as an absolute distance from the origin.
+     */
+    public final int getFinalX() {
+        return mScrollerX.mFinal;
+    }
+
+    /**
+     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     *
+     * @return The final Y offset as an absolute distance from the origin.
+     */
+    public final int getFinalY() {
+        return mScrollerY.mFinal;
+    }
+
+    /**
+     * Returns how long the scroll event will take, in milliseconds.
+     *
+     * @return The duration of the scroll in milliseconds.
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScrollers don't necessarily have a fixed duration.
+     *             This function will lie to the best of its ability.
+     */
+    @Deprecated
+    public final int getDuration() {
+        return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
+    }
+
+    /**
+     * Extend the scroll animation. This allows a running animation to scroll
+     * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}.
+     *
+     * @param extend Additional time to scroll in milliseconds.
+     * @see #setFinalX(int)
+     * @see #setFinalY(int)
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScrollers don't necessarily have a fixed duration.
+     *             Instead of setting a new final position and extending
+     *             the duration of an existing scroll, use startScroll
+     *             to begin a new animation.
+     */
+    @Deprecated
+    public void extendDuration(int extend) {
+        mScrollerX.extendDuration(extend);
+        mScrollerY.extendDuration(extend);
+    }
+
+    /**
+     * Sets the final position (X) for this scroller.
+     *
+     * @param newX The new X offset as an absolute distance from the origin.
+     * @see #extendDuration(int)
+     * @see #setFinalY(int)
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScroller's final position may change during an animation.
+     *             Instead of setting a new final position and extending
+     *             the duration of an existing scroll, use startScroll
+     *             to begin a new animation.
+     */
+    @Deprecated
+    public void setFinalX(int newX) {
+        mScrollerX.setFinalPosition(newX);
+    }
+
+    /**
+     * Sets the final position (Y) for this scroller.
+     *
+     * @param newY The new Y offset as an absolute distance from the origin.
+     * @see #extendDuration(int)
+     * @see #setFinalX(int)
+     *
+     * @hide Pending removal once nothing depends on it
+     * @deprecated OverScroller's final position may change during an animation.
+     *             Instead of setting a new final position and extending
+     *             the duration of an existing scroll, use startScroll
+     *             to begin a new animation.
+     */
+    @Deprecated
+    public void setFinalY(int newY) {
+        mScrollerY.setFinalPosition(newY);
+    }
+
+    /**
+     * Call this when you want to know the new location. If it returns true, the
+     * animation is not yet finished.
+     */
+    public boolean computeScrollOffset() {
+        if (isFinished()) {
+            return false;
+        }
+
+        switch (mMode) {
+            case SCROLL_MODE:
+                long time = AnimationUtils.currentAnimationTimeMillis();
+                // Any scroller can be used for time, since they were started
+                // together in scroll mode. We use X here.
+                final long elapsedTime = time - mScrollerX.mStartTime;
+
+                final int duration = mScrollerX.mDuration;
+                if (elapsedTime < duration) {
+                    float q = (float) (elapsedTime) / duration;
+
+                    q = Scroller.viscousFluid(q);
+
+                    mScrollerX.updateScroll(q);
+                    mScrollerY.updateScroll(q);
+                } else {
+                    abortAnimation();
+                }
+                break;
+
+            case FLING_MODE:
+                if (!mScrollerX.mFinished) {
+                    if (!mScrollerX.update()) {
+                        if (!mScrollerX.continueWhenFinished()) {
+                            mScrollerX.finish();
+                        }
+                    }
+                }
+
+                if (!mScrollerY.mFinished) {
+                    if (!mScrollerY.update()) {
+                        if (!mScrollerY.continueWhenFinished()) {
+                            mScrollerY.finish();
+                        }
+                    }
+                }
+
+                break;
+        }
+
+        return true;
+    }
+
+    /**
+     * 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.
+     *
+     * @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.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy) {
+        startScroll(startX, startY, dx, dy, DEFAULT_DURATION);
+    }
+
+    /**
+     * Start scrolling by providing a starting point and the distance to travel.
+     *
+     * @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 duration Duration of the scroll in milliseconds.
+     */
+    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
+        mMode = SCROLL_MODE;
+        mScrollerX.startScroll(startX, dx, duration);
+        mScrollerY.startScroll(startY, dy, duration);
+    }
+
+    /**
+     * Call this when you want to 'spring back' into a valid coordinate range.
+     *
+     * @param startX Starting X coordinate
+     * @param startY Starting Y coordinate
+     * @param minX Minimum valid X value
+     * @param maxX Maximum valid X value
+     * @param minY Minimum valid Y value
+     * @param maxY Minimum valid Y value
+     * @return true if a springback was initiated, false if startX and startY were
+     *          already within the valid range.
+     */
+    public boolean springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+        mMode = FLING_MODE;
+
+        // Make sure both methods are called.
+        final boolean spingbackX = mScrollerX.springback(startX, minX, maxX);
+        final boolean spingbackY = mScrollerY.springback(startY, minY, maxY);
+        return spingbackX || spingbackY;
+    }
+
+    public void fling(int startX, int startY, int velocityX, int velocityY,
+            int minX, int maxX, int minY, int maxY) {
+        fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
+    }
+
+    /**
+     * Start scrolling based on a fling gesture. The distance traveled will
+     * depend on the initial velocity of the fling.
+     *
+     * @param startX Starting point of the scroll (X)
+     * @param startY Starting point of the scroll (Y)
+     * @param velocityX Initial velocity of the fling (X) measured in pixels per
+     *            second.
+     * @param velocityY Initial velocity of the fling (Y) measured in pixels per
+     *            second
+     * @param minX Minimum X value. The scroller will not scroll past this point
+     *            unless overX > 0. If overfling is allowed, it will use minX as
+     *            a springback boundary.
+     * @param maxX Maximum X value. The scroller will not scroll past this point
+     *            unless overX > 0. If overfling is allowed, it will use maxX as
+     *            a springback boundary.
+     * @param minY Minimum Y value. The scroller will not scroll past this point
+     *            unless overY > 0. If overfling is allowed, it will use minY as
+     *            a springback boundary.
+     * @param maxY Maximum Y value. The scroller will not scroll past this point
+     *            unless overY > 0. If overfling is allowed, it will use maxY as
+     *            a springback boundary.
+     * @param overX Overfling range. If > 0, horizontal overfling in either
+     *            direction will be possible.
+     * @param overY Overfling range. If > 0, vertical overfling in either
+     *            direction will be possible.
+     */
+    public void fling(int startX, int startY, int velocityX, int velocityY,
+            int minX, int maxX, int minY, int maxY, int overX, int overY) {
+        // Continue a scroll or fling in progress
+        if (mFlywheel && !isFinished()) {
+            float oldVelocityX = mScrollerX.mCurrVelocity;
+            float oldVelocityY = mScrollerY.mCurrVelocity;
+            if (Math.signum(velocityX) == Math.signum(oldVelocityX) &&
+                    Math.signum(velocityY) == Math.signum(oldVelocityY)) {
+                velocityX += oldVelocityX;
+                velocityY += oldVelocityY;
+            }
+        }
+
+        mMode = FLING_MODE;
+        mScrollerX.fling(startX, velocityX, minX, maxX, overX);
+        mScrollerY.fling(startY, velocityY, minY, maxY, overY);
+    }
+
+    /**
+     * Notify the scroller that we've reached a horizontal boundary.
+     * Normally the information to handle this will already be known
+     * when the animation is started, such as in a call to one of the
+     * fling functions. However there are cases where this cannot be known
+     * in advance. This function will transition the current motion and
+     * animate from startX to finalX as appropriate.
+     *
+     * @param startX Starting/current X position
+     * @param finalX Desired final X position
+     * @param overX Magnitude of overscroll allowed. This should be the maximum
+     *              desired distance from finalX. Absolute value - must be positive.
+     */
+    public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
+        mScrollerX.notifyEdgeReached(startX, finalX, overX);
+    }
+
+    /**
+     * Notify the scroller that we've reached a vertical boundary.
+     * Normally the information to handle this will already be known
+     * when the animation is started, such as in a call to one of the
+     * fling functions. However there are cases where this cannot be known
+     * in advance. This function will animate a parabolic motion from
+     * startY to finalY.
+     *
+     * @param startY Starting/current Y position
+     * @param finalY Desired final Y position
+     * @param overY Magnitude of overscroll allowed. This should be the maximum
+     *              desired distance from finalY. Absolute value - must be positive.
+     */
+    public void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
+        mScrollerY.notifyEdgeReached(startY, finalY, overY);
+    }
+
+    /**
+     * Returns whether the current Scroller is currently returning to a valid position.
+     * Valid bounds were provided by the
+     * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
+     *
+     * One should check this value before calling
+     * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress
+     * to restore a valid position will then be stopped. The caller has to take into account
+     * the fact that the started scroll will start from an overscrolled position.
+     *
+     * @return true when the current position is overscrolled and in the process of
+     *         interpolating back to a valid value.
+     */
+    public boolean isOverScrolled() {
+        return ((!mScrollerX.mFinished &&
+                mScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
+                (!mScrollerY.mFinished &&
+                        mScrollerY.mState != MagneticOverScroller.TO_EDGE));
+    }
+
+    /**
+     * Stops the animation. Contrary to {@link #forceFinished(boolean)},
+     * aborting the animating causes the scroller to move to the final x and y
+     * positions.
+     *
+     * @see #forceFinished(boolean)
+     */
+    public void abortAnimation() {
+        mScrollerX.finish();
+        mScrollerY.finish();
+    }
+
+    /**
+     * Returns the time elapsed since the beginning of the scrolling.
+     *
+     * @return The elapsed time in milliseconds.
+     *
+     * @hide
+     */
+    public int timePassed() {
+        final long time = AnimationUtils.currentAnimationTimeMillis();
+        final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
+        return (int) (time - startTime);
+    }
+
+    /**
+     * @hide
+     */
+    public boolean isScrollingInDirection(float xvel, float yvel) {
+        final int dx = mScrollerX.mFinal - mScrollerX.mStart;
+        final int dy = mScrollerY.mFinal - mScrollerY.mStart;
+        return !isFinished() && Math.signum(xvel) == Math.signum(dx) &&
+        Math.signum(yvel) == Math.signum(dy);
+    }
+
+    class MagneticOverScroller {
+        // Initial position
+        int mStart;
+
+        // Current position
+        int mCurrentPosition;
+
+        // Final position
+        int mFinal;
+
+        // Initial velocity
+        int mVelocity;
+
+        // Current velocity
+        float mCurrVelocity;
+
+        // Constant current deceleration
+        float mDeceleration;
+
+        // Animation starting time, in system milliseconds
+        long mStartTime;
+
+        // Animation duration, in milliseconds
+        int mDuration;
+
+        // Duration to complete spline component of animation
+        int mSplineDuration;
+
+        // Distance to travel along spline animation
+        int mSplineDistance;
+
+        // Whether the animation is currently in progress
+        boolean mFinished;
+
+        private static final int TO_EDGE = 0;
+        private static final int TO_BOUNDARY = 1;
+        private static final int TO_BOUNCE = 2;
+
+        private int mState = TO_EDGE;
+
+        // The allowed overshot distance before boundary is reached.
+        private int mOver;
+
+        // If the velocity is smaller than this value, no bounce is triggered
+        // when the edge limits are reached (would result in a zero pixels
+        // displacement anyway).
+        private static final float MINIMUM_VELOCITY_FOR_BOUNCE = 140.0f; //Float.MAX_VALUE;//140.0f;
+
+        // Proportion of the velocity that is preserved when the edge is reached.
+        private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.36f;
+
+        private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT;
+
+        MagneticOverScroller() {
+            mFinished = true;
+        }
+
+        void updateScroll(float q) {
+            mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+        }
+
+        /*
+         * Get a signed deceleration that will reduce the velocity.
+         */
+        float getDeceleration(int velocity) {
+            return velocity > 0 ? -OverScroller.this.mDeceleration : OverScroller.this.mDeceleration;
+        }
+
+        /*
+         * Modifies mDuration to the duration it takes to get from start to newFinal using the
+         * spline interpolation. The previous duration was needed to get to oldFinal.
+         */
+        void adjustDuration(int start, int oldFinal, int newFinal) {
+            final int oldDistance = oldFinal - start;
+            final int newDistance = newFinal - start;
+            final float x = (float) Math.abs((float) newDistance / oldDistance);
+            final int index = (int) (NB_SAMPLES * x);
+            if (index < NB_SAMPLES) {
+                final float x_inf = (float) index / NB_SAMPLES;
+                final float x_sup = (float) (index + 1) / NB_SAMPLES;
+                final float t_inf = SPLINE_TIME[index];
+                final float t_sup = SPLINE_TIME[index + 1];
+                final float timeCoef = t_inf + (x - x_inf) / (x_sup - x_inf) * (t_sup - t_inf);
+
+                mDuration *= timeCoef;
+            }
+        }
+
+        void startScroll(int start, int distance, int duration) {
+            mFinished = false;
+
+            mStart = start;
+            mFinal = start + distance;
+
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mDuration = duration;
+
+            // Unused
+            mDeceleration = 0.0f;
+            mVelocity = 0;
+        }
+
+        void finish() {
+            mCurrentPosition = mFinal;
+            // Not reset since WebView relies on this value for fast fling.
+            // TODO: restore when WebView uses the fast fling implemented in this class.
+            // mCurrVelocity = 0.0f;
+            mFinished = true;
+        }
+
+        void setFinalPosition(int position) {
+            mFinal = position;
+            mFinished = false;
+        }
+
+        void extendDuration(int extend) {
+            final long time = AnimationUtils.currentAnimationTimeMillis();
+            final int elapsedTime = (int) (time - mStartTime);
+            mDuration = elapsedTime + extend;
+            mFinished = false;
+        }
+
+        void setBounceCoefficient(float coefficient) {
+            mBounceCoefficient = coefficient;
+        }
+
+        boolean springback(int start, int min, int max) {
+            mFinished = true;
+
+            mStart = mFinal = start;
+            mVelocity = 0;
+
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mDuration = 0;
+
+            if (start < min) {
+                startSpringback(start, min, 0);
+            } else if (start > max) {
+                startSpringback(start, max, 0);
+            }
+
+            return !mFinished;
+        }
+
+        private void startSpringback(int start, int end, int velocity) {
+            mFinished = false;
+            mState = TO_BOUNCE;
+            mStart = mFinal = end;
+            final float velocitySign = Math.signum(start - end);
+            mDeceleration = getDeceleration((int) velocitySign);
+            fitOnBounceCurve(start, end, velocity);
+            mDuration = - (int) (2000.0f * mVelocity / mDeceleration);
+        }
+
+        void fling(int start, int velocity, int min, int max, int over) {
+            mOver = over;
+            mFinished = false;
+            mCurrVelocity = mVelocity = velocity;
+            mDuration = mSplineDuration = 0;
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mStart = start;
+
+            if (start > max || start < min) {
+                startAfterEdge(start, min, max, velocity);
+                return;
+            }
+
+            mState = TO_EDGE;
+            double totalDistance = 0.0;
+
+            if (velocity != 0) {
+                final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA);
+                // Duration are expressed in milliseconds
+                mDuration = mSplineDuration = (int) (1000.0 * Math.exp(l / (DECELERATION_RATE - 1.0)));
+                totalDistance = (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+            }
+
+            mSplineDistance = (int) (totalDistance * Math.signum(velocity));
+            mFinal = start + mSplineDistance;
+
+            // Clamp to a valid final position
+            if (mFinal < min) {
+                adjustDuration(mStart, mFinal, min);
+                mFinal = min;
+            }
+
+            if (mFinal > max) {
+                adjustDuration(mStart, mFinal, max);
+                mFinal = max;
+            }
+        }
+
+        private void fitOnBounceCurve(int start, int end, int velocity) {
+            // Simulate a bounce that started from edge
+            final float durationToApex = - velocity / mDeceleration;
+            final float distanceToApex = velocity * velocity / 2.0f / Math.abs(mDeceleration);
+            final float distanceToEdge = Math.abs(end - start);
+            final float totalDuration = (float) Math.sqrt(
+                    2.0 * (distanceToApex + distanceToEdge) / Math.abs(mDeceleration));
+            mStartTime -= (int) (1000.0f * (totalDuration - durationToApex));
+            mStart = end;
+            mVelocity = (int) (- mDeceleration * totalDuration);
+        }
+
+        private void startBounceAfterEdge(int start, int end, int velocity) {
+            mDeceleration = getDeceleration(velocity == 0 ? start - end : velocity);
+            fitOnBounceCurve(start, end, velocity);
+            onEdgeReached();
+        }
+
+        private void startAfterEdge(int start, int min, int max, int velocity) {
+            if (start > min && start < max) {
+                mFinished = true;
+                return;
+            }
+            final boolean positive = start > max;
+            final int edge = positive ? max : min;
+            final int overDistance = start - edge;
+            boolean keepIncreasing = overDistance * velocity >= 0;
+            if (keepIncreasing) {
+                // Will result in a bounce or a to_boundary depending on velocity.
+                startBounceAfterEdge(start, edge, velocity);
+            } else {
+                final double l = Math.log(START_TENSION * Math.abs(velocity) / ALPHA);
+                final double totalDistance =
+                    (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
+                if (totalDistance > Math.abs(overDistance)) {
+                    fling(start, velocity, positive ? min : start, positive ? start : max, mOver);
+                } else {
+                    startSpringback(start, edge, velocity);
+                }
+            }
+        }
+
+        void notifyEdgeReached(int start, int end, int over) {
+            mOver = over;
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            // We were in fling/scroll mode before: current velocity is such that distance to edge
+            // is increasing. Ensures that startAfterEdge will not start a new fling.
+            startAfterEdge(start, end, end, (int) mCurrVelocity);
+        }
+
+        private void onEdgeReached() {
+            // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+            final float distance = - mVelocity * mVelocity / (2.0f * mDeceleration);
+
+            if (Math.abs(distance) < mOver) {
+                // Spring force will bring us back to final position
+                mState = TO_BOUNCE;
+                mFinal = mStart;
+                mDuration = - (int) (2000.0f * mVelocity / mDeceleration);
+            } else {
+                // Velocity is too high, we will hit the boundary limit
+                mState = TO_BOUNDARY;
+                int over = mVelocity > 0 ? mOver : -mOver;
+                mFinal = mStart + over;
+                mDuration = (int) (1000.0 * Math.PI * over / 2.0 / mVelocity);
+            }
+        }
+
+        boolean continueWhenFinished() {
+            switch (mState) {
+                case TO_EDGE:
+                    // Duration from start to null velocity
+                    if (mDuration < mSplineDuration) {
+                        // If the animation was clamped, we reached the edge
+                        mStart = mFinal;
+                        // Speed when edge was reached
+                        mVelocity = (int) mCurrVelocity;
+                        mDeceleration = getDeceleration(mVelocity);
+                        mStartTime += mDuration;
+                        onEdgeReached();
+                    } else {
+                        // Normal stop, no need to continue
+                        return false;
+                    }
+                    break;
+                case TO_BOUNDARY:
+                    mStartTime += mDuration;
+                    startSpringback(mFinal, mFinal - (mVelocity > 0 ? mOver:-mOver), 0);
+                    break;
+                case TO_BOUNCE:
+                    mVelocity = (int) (mVelocity * mBounceCoefficient);
+                    if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) {
+                        return false;
+                    }
+                    mStartTime += mDuration;
+                    mDuration = - (int) (mVelocity / mDeceleration);
+                    break;
+            }
+
+            update();
+            return true;
+        }
+
+        /*
+         * Update the current position and velocity for current time. Returns
+         * true if update has been done and false if animation duration has been
+         * reached.
+         */
+        boolean update() {
+            final long time = AnimationUtils.currentAnimationTimeMillis();
+            final long currentTime = time - mStartTime;
+
+            if (currentTime > mDuration) {
+                return false;
+            }
+
+            double distance = 0.0;
+            switch (mState) {
+                case TO_EDGE: {
+                    final float t = (float) currentTime / mSplineDuration;
+                    final int index = (int) (NB_SAMPLES * t);
+                    float distanceCoef = 1.f;
+                    float velocityCoef = 0.f;
+                    if (index < NB_SAMPLES) {
+                        final float t_inf = (float) index / NB_SAMPLES;
+                        final float t_sup = (float) (index + 1) / NB_SAMPLES;
+                        final float d_inf = SPLINE_POSITION[index];
+                        final float d_sup = SPLINE_POSITION[index + 1];
+                        velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
+                        distanceCoef = d_inf + (t - t_inf) * velocityCoef;
+                    }
+
+                    distance = distanceCoef * mSplineDistance;
+                    mCurrVelocity = velocityCoef * mSplineDistance / mSplineDuration * 1000;
+                    break;
+                }
+
+                case TO_BOUNCE: {
+                    final float t = currentTime / 1000.0f;
+                    mCurrVelocity = mVelocity + mDeceleration * t;
+                    distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+                    break;
+                }
+
+                case TO_BOUNDARY: {
+                    final float t = currentTime / 1000.0f;
+                    final float d = t * Math.abs(mVelocity) / mOver;
+                    mCurrVelocity = mVelocity * (float) Math.cos(d);
+                    distance = (mVelocity > 0 ? mOver : -mOver) * Math.sin(d);
+                    break;
+                }
+            }
+
+            mCurrentPosition = mStart + (int) Math.round(distance);
+            return true;
+        }
+    }
+}
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index eb527eb..900c9ec 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -19,8 +19,12 @@
 import com.android.internal.R;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.content.res.TypedArray;
+import android.graphics.Canvas;
 import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.StrictMode;
 import android.util.AttributeSet;
 import android.view.FocusFinder;
 import android.view.KeyEvent;
@@ -61,7 +65,9 @@
     private long mLastScroll;
 
     private final Rect mTempRect = new Rect();
-    private Scroller mScroller;
+    private OverScroller mScroller;
+    private EdgeGlow mEdgeGlowTop;
+    private EdgeGlow mEdgeGlowBottom;
 
     /**
      * Flag to indicate that we are moving focus ourselves. This is so the
@@ -115,12 +121,24 @@
     private int mMinimumVelocity;
     private int mMaximumVelocity;
     
+    private int mOverscrollDistance;
+    private int mOverflingDistance;
+
     /**
      * ID of the active pointer. This is used to retain consistency during
      * drags/flings if multiple pointers are used.
      */
     private int mActivePointerId = INVALID_POINTER;
-    
+
+    /**
+     * The StrictMode "critical time span" objects to catch animation
+     * stutters.  Non-null when a time-sensitive animation is
+     * in-flight.  Must call finish() on them when done animating.
+     * These are no-ops on user builds.
+     */
+    private StrictMode.Span mScrollStrictSpan = null;  // aka "drag"
+    private StrictMode.Span mFlingStrictSpan = null;
+
     /**
      * Sentinel value for no current active pointer.
      * Used by {@link #mActivePointerId}.
@@ -187,7 +205,7 @@
 
 
     private void initScrollView() {
-        mScroller = new Scroller(getContext());
+        mScroller = new OverScroller(getContext());
         setFocusable(true);
         setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
         setWillNotDraw(false);
@@ -195,6 +213,8 @@
         mTouchSlop = configuration.getScaledTouchSlop();
         mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
         mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
+        mOverscrollDistance = configuration.getScaledOverscrollDistance();
+        mOverflingDistance = configuration.getScaledOverflingDistance();
     }
 
     @Override
@@ -427,6 +447,9 @@
                 if (yDiff > mTouchSlop) {
                     mIsBeingDragged = true;
                     mLastMotionY = y;
+                    if (mScrollStrictSpan == null) {
+                        mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
+                    }
                 }
                 break;
             }
@@ -451,6 +474,9 @@
                 * being flinged.
                 */
                 mIsBeingDragged = !mScroller.isFinished();
+                if (mIsBeingDragged && mScrollStrictSpan == null) {
+                    mScrollStrictSpan = StrictMode.enterCriticalSpan("ScrollView-scroll");
+                }
                 break;
             }
 
@@ -459,6 +485,9 @@
                 /* Release the drag */
                 mIsBeingDragged = false;
                 mActivePointerId = INVALID_POINTER;
+                if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
+                    invalidate();
+                }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
                 onSecondaryPointerUp(ev);
@@ -491,9 +520,7 @@
         switch (action & MotionEvent.ACTION_MASK) {
             case MotionEvent.ACTION_DOWN: {
                 final float y = ev.getY();
-                if (!(mIsBeingDragged = inChild((int) ev.getX(), (int) y))) {
-                    return false;
-                }
+                mIsBeingDragged = true;
                 
                 /*
                  * If being flinged and user touches, stop the fling. isFinished
@@ -501,6 +528,10 @@
                  */
                 if (!mScroller.isFinished()) {
                     mScroller.abortAnimation();
+                    if (mFlingStrictSpan != null) {
+                        mFlingStrictSpan.finish();
+                        mFlingStrictSpan = null;
+                    }
                 }
 
                 // Remember where the motion event started
@@ -516,7 +547,36 @@
                     final int deltaY = (int) (mLastMotionY - y);
                     mLastMotionY = y;
 
-                    scrollBy(0, deltaY);
+                    final int oldX = mScrollX;
+                    final int oldY = mScrollY;
+                    final int range = getScrollRange();
+                    if (overScrollBy(0, deltaY, 0, mScrollY, 0, range,
+                            0, mOverscrollDistance, true)) {
+                        // Break our velocity if we hit a scroll barrier.
+                        mVelocityTracker.clear();
+                    }
+                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+                    final int overscrollMode = getOverScrollMode();
+                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+                        final int pulledToY = oldY + deltaY;
+                        if (pulledToY < 0) {
+                            mEdgeGlowTop.onPull((float) deltaY / getHeight());
+                            if (!mEdgeGlowBottom.isFinished()) {
+                                mEdgeGlowBottom.onRelease();
+                            }
+                        } else if (pulledToY > range) {
+                            mEdgeGlowBottom.onPull((float) deltaY / getHeight());
+                            if (!mEdgeGlowTop.isFinished()) {
+                                mEdgeGlowTop.onRelease();
+                            }
+                        }
+                        if (mEdgeGlowTop != null
+                                && (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished())) {
+                            invalidate();
+                        }
+                    }
                 }
                 break;
             case MotionEvent.ACTION_UP: 
@@ -525,27 +585,28 @@
                     velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                     int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
 
-                    if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) {
-                        fling(-initialVelocity);
+                    if (getChildCount() > 0) {
+                        if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
+                            fling(-initialVelocity);
+                        } else {
+                            final int bottom = getScrollRange();
+                            if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, bottom)) {
+                                invalidate();
+                            }
+                        }
                     }
 
                     mActivePointerId = INVALID_POINTER;
-                    mIsBeingDragged = false;
-
-                    if (mVelocityTracker != null) {
-                        mVelocityTracker.recycle();
-                        mVelocityTracker = null;
-                    }
+                    endDrag();
                 }
                 break;
             case MotionEvent.ACTION_CANCEL:
                 if (mIsBeingDragged && getChildCount() > 0) {
-                    mActivePointerId = INVALID_POINTER;
-                    mIsBeingDragged = false;
-                    if (mVelocityTracker != null) {
-                        mVelocityTracker.recycle();
-                        mVelocityTracker = null;
+                    if (mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) {
+                        invalidate();
                     }
+                    mActivePointerId = INVALID_POINTER;
+                    endDrag();
                 }
                 break;
             case MotionEvent.ACTION_POINTER_UP:
@@ -572,6 +633,32 @@
         }
     }
     
+    @Override
+    protected void onOverScrolled(int scrollX, int scrollY,
+            boolean clampedX, boolean clampedY) {
+        // Treat animating scrolls differently; see #computeScroll() for why.
+        if (!mScroller.isFinished()) {
+            mScrollX = scrollX;
+            mScrollY = scrollY;
+            if (clampedY) {
+                mScroller.springBack(mScrollX, mScrollY, 0, 0, 0, getScrollRange());
+            }
+        } else {
+            super.scrollTo(scrollX, scrollY);
+        }
+        awakenScrollBars();
+    }
+
+    private int getScrollRange() {
+        int scrollRange = 0;
+        if (getChildCount() > 0) {
+            View child = getChildAt(0);
+            scrollRange = Math.max(0,
+                    child.getHeight() - (getHeight() - mPaddingBottom - mPaddingTop));
+        }
+        return scrollRange;
+    }
+
     /**
      * <p>
      * Finds the next focusable component that fits in this View's bounds
@@ -920,6 +1007,10 @@
         } else {
             if (!mScroller.isFinished()) {
                 mScroller.abortAnimation();
+                if (mFlingStrictSpan != null) {
+                    mFlingStrictSpan.finish();
+                    mFlingStrictSpan = null;
+                }
             }
             scrollBy(dx, dy);
         }
@@ -948,7 +1039,16 @@
             return contentHeight;
         }
         
-        return getChildAt(0).getBottom();
+        int scrollRange = getChildAt(0).getBottom();
+        final int scrollY = mScrollY;
+        final int overscrollBottom = Math.max(0, scrollRange - contentHeight);
+        if (scrollY < 0) {
+            scrollRange -= scrollY;
+        } else if (scrollY > overscrollBottom) {
+            scrollRange += scrollY - overscrollBottom;
+        }
+
+        return scrollRange;
     }
 
     @Override
@@ -1009,20 +1109,31 @@
             int x = mScroller.getCurrX();
             int y = mScroller.getCurrY();
 
-            if (getChildCount() > 0) {
-                View child = getChildAt(0);
-                x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
-                y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
-                if (x != oldX || y != oldY) {
-                    mScrollX = x;
-                    mScrollY = y;
-                    onScrollChanged(x, y, oldX, oldY);
+            if (oldX != x || oldY != y) {
+                overScrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(),
+                        0, mOverflingDistance, false);
+                onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+
+                final int range = getScrollRange();
+                final int overscrollMode = getOverScrollMode();
+                if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                        (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0)) {
+                    if (y < 0 && oldY >= 0) {
+                        mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity());
+                    } else if (y > range && oldY <= range) {
+                        mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity());
+                    }
                 }
             }
             awakenScrollBars();
 
             // Keep on drawing until the animation has finished.
             postInvalidate();
+        } else {
+            if (mFlingStrictSpan != null) {
+                mFlingStrictSpan.finish();
+                mFlingStrictSpan = null;
+            }
         }
     }
 
@@ -1197,6 +1308,20 @@
     }
 
     @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+
+        if (mScrollStrictSpan != null) {
+            mScrollStrictSpan.finish();
+            mScrollStrictSpan = null;
+        }
+        if (mFlingStrictSpan != null) {
+            mFlingStrictSpan.finish();
+            mFlingStrictSpan = null;
+        }
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
         mIsLayoutDirty = false;
@@ -1254,7 +1379,7 @@
             int bottom = getChildAt(0).getHeight();
     
             mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, 
-                    Math.max(0, bottom - height));
+                    Math.max(0, bottom - height), 0, height/2);
     
             final boolean movingDown = velocityY > 0;
     
@@ -1269,11 +1394,34 @@
                 mScrollViewMovedFocus = true;
                 mScrollViewMovedFocus = false;
             }
-    
+
+            if (mFlingStrictSpan == null) {
+                mFlingStrictSpan = StrictMode.enterCriticalSpan("ScrollView-fling");
+            }
+
             invalidate();
         }
     }
 
+    private void endDrag() {
+        mIsBeingDragged = false;
+
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+
+        if (mEdgeGlowTop != null) {
+            mEdgeGlowTop.onRelease();
+            mEdgeGlowBottom.onRelease();
+        }
+
+        if (mScrollStrictSpan != null) {
+            mScrollStrictSpan.finish();
+            mScrollStrictSpan = null;
+        }
+    }
+
     /**
      * {@inheritDoc}
      *
@@ -1292,6 +1440,55 @@
         }
     }
 
+    @Override
+    public void setOverScrollMode(int mode) {
+        if (mode != OVER_SCROLL_NEVER) {
+            if (mEdgeGlowTop == null) {
+                final Resources res = getContext().getResources();
+                final Drawable edge = res.getDrawable(R.drawable.overscroll_edge);
+                final Drawable glow = res.getDrawable(R.drawable.overscroll_glow);
+                mEdgeGlowTop = new EdgeGlow(edge, glow);
+                mEdgeGlowBottom = new EdgeGlow(edge, glow);
+            }
+        } else {
+            mEdgeGlowTop = null;
+            mEdgeGlowBottom = null;
+        }
+        super.setOverScrollMode(mode);
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        super.draw(canvas);
+        if (mEdgeGlowTop != null) {
+            final int scrollY = mScrollY;
+            if (!mEdgeGlowTop.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth();
+
+                canvas.translate(-width / 2, Math.min(0, scrollY));
+                mEdgeGlowTop.setSize(width * 2, getHeight());
+                if (mEdgeGlowTop.draw(canvas)) {
+                    invalidate();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+            if (!mEdgeGlowBottom.isFinished()) {
+                final int restoreCount = canvas.save();
+                final int width = getWidth();
+                final int height = getHeight();
+
+                canvas.translate(-width / 2, Math.max(getScrollRange(), scrollY) + height);
+                canvas.rotate(180, width, 0);
+                mEdgeGlowBottom.setSize(width * 2, height);
+                if (mEdgeGlowBottom.draw(canvas)) {
+                    invalidate();
+                }
+                canvas.restoreToCount(restoreCount);
+            }
+        }
+    }
+
     private int clamp(int n, int my, int child) {
         if (my >= child || n < 0) {
             /* my >= child is this case:
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index b1f50ba..f00640e 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -52,14 +52,10 @@
     private float mDurationReciprocal;
     private float mDeltaX;
     private float mDeltaY;
-    private float mViscousFluidScale;
-    private float mViscousFluidNormalize;
     private boolean mFinished;
     private Interpolator mInterpolator;
     private boolean mFlywheel;
 
-    private float mCoeffX = 0.0f;
-    private float mCoeffY = 1.0f;
     private float mVelocity;
 
     private static final int DEFAULT_DURATION = 250;
@@ -94,8 +90,17 @@
             SPLINE[i] = d;
         }
         SPLINE[NB_SAMPLES] = 1.0f;
+
+        // This controls the viscous fluid effect (how much of it)
+        sViscousFluidScale = 8.0f;
+        // must be set to 1.0 (used in viscousFluid())
+        sViscousFluidNormalize = 1.0f;
+        sViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
     }
 
+    private static float sViscousFluidScale;
+    private static float sViscousFluidNormalize;
+
     /**
      * Create a Scroller with the default duration and interpolator.
      */
@@ -103,6 +108,11 @@
         this(context, null);
     }
 
+    /**
+     * Create a Scroller with the specified interpolator. If the interpolator is
+     * null, the default (viscous) interpolator will be used. "Flywheel" behavior will
+     * be in effect for apps targeting Honeycomb or newer.
+     */
     public Scroller(Context context, Interpolator interpolator) {
         this(context, interpolator,
                 context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
@@ -110,7 +120,8 @@
 
     /**
      * Create a Scroller with the specified interpolator. If the interpolator is
-     * null, the default (viscous) interpolator will be used.
+     * null, the default (viscous) interpolator will be used. Specify whether or
+     * not to support progressive "flywheel" behavior in flinging.
      */
     public Scroller(Context context, Interpolator interpolator, boolean flywheel) {
         mFinished = true;
@@ -332,12 +343,7 @@
         mFinalY = startY + dy;
         mDeltaX = dx;
         mDeltaY = dy;
-        mDurationReciprocal = 1.0f / mDuration;
-        // This controls the viscous fluid effect (how much of it)
-        mViscousFluidScale = 8.0f;
-        // must be set to 1.0 (used in viscousFluid())
-        mViscousFluidNormalize = 1.0f;
-        mViscousFluidNormalize = 1.0f / viscousFluid(1.0f);
+        mDurationReciprocal = 1.0f / (float) mDuration;
     }
 
     /**
@@ -393,8 +399,8 @@
         mStartX = startX;
         mStartY = startY;
 
-        mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 
-        mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
+        float coeffX = velocity == 0 ? 1.0f : velocityX / velocity;
+        float coeffY = velocity == 0 ? 1.0f : velocityY / velocity;
 
         int totalDistance =
                 (int) (ALPHA * Math.exp(DECELERATION_RATE / (DECELERATION_RATE - 1.0) * l));
@@ -404,22 +410,20 @@
         mMinY = minY;
         mMaxY = maxY;
 
-        mFinalX = startX + Math.round(totalDistance * mCoeffX);
+        mFinalX = startX + Math.round(totalDistance * coeffX);
         // Pin to mMinX <= mFinalX <= mMaxX
         mFinalX = Math.min(mFinalX, mMaxX);
         mFinalX = Math.max(mFinalX, mMinX);
         
-        mFinalY = startY + Math.round(totalDistance * mCoeffY);
+        mFinalY = startY + Math.round(totalDistance * coeffY);
         // Pin to mMinY <= mFinalY <= mMaxY
         mFinalY = Math.min(mFinalY, mMaxY);
         mFinalY = Math.max(mFinalY, mMinY);
     }
     
-    
-    
-    private float viscousFluid(float x)
+    static float viscousFluid(float x)
     {
-        x *= mViscousFluidScale;
+        x *= sViscousFluidScale;
         if (x < 1.0f) {
             x -= (1.0f - (float)Math.exp(-x));
         } else {
@@ -427,7 +431,7 @@
             x = 1.0f - (float)Math.exp(1.0f - x);
             x = start + x * (1.0f - start);
         }
-        x *= mViscousFluidNormalize;
+        x *= sViscousFluidNormalize;
         return x;
     }
     
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 78f3cd9..9f9fb18 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -7565,9 +7565,12 @@
     }
 
     private void selectCurrentWord() {
-        // In case selection mode is started after an orientation change or after a select all,
-        // use the current selection instead of creating one
-        if (hasSelection()) {
+        if (hasPasswordTransformationMethod()) {
+            // selectCurrentWord is not available on a password field and would return an
+            // arbitrary 10-charater selection around pressed position. Select all instead.
+            // Note that cut/copy menu entries are not available for passwords.
+            // This is however useful to delete or paste to replace the entire content.
+            Selection.setSelection((Spannable) mText, 0, mText.length());
             return;
         }
 
@@ -7835,13 +7838,19 @@
             return true;
         }
 
-        if (mSelectionActionMode != null && touchPositionIsInSelection()) {
-            final int start = getSelectionStart();
-            final int end = getSelectionEnd();
-            CharSequence selectedText = mTransformed.subSequence(start, end);
-            ClipData data = ClipData.newPlainText(null, null, selectedText);
-            startDrag(data, getTextThumbnailBuilder(selectedText), false);
-            stopSelectionActionMode();
+        if (mSelectionActionMode != null) {
+            if (touchPositionIsInSelection()) {
+                // Start a drag
+                final int start = getSelectionStart();
+                final int end = getSelectionEnd();
+                CharSequence selectedText = mTransformed.subSequence(start, end);
+                ClipData data = ClipData.newPlainText(null, null, selectedText);
+                startDrag(data, getTextThumbnailBuilder(selectedText), false);
+                stopSelectionActionMode();
+            } else {
+                selectCurrentWord();
+                getSelectionController().show();
+            }
             performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
             mEatTouchRelease = true;
             return true;
@@ -7886,9 +7895,7 @@
      * mode is not available.
      */
     private ActionMode.Callback getActionModeCallback() {
-        // Long press in the current selection.
-        // Should initiate a drag. Return false, to rely on context menu for now.
-        if (canSelectText() && !touchPositionIsInSelection()) {
+        if (canSelectText()) {
             return new SelectionActionModeCallback();
         }
         return null;
@@ -7983,15 +7990,7 @@
             boolean atLeastOne = false;
 
             if (canSelectText()) {
-                if (hasPasswordTransformationMethod()) {
-                    // selectCurrentWord is not available on a password field and would return an
-                    // arbitrary 10-charater selection around pressed position. Select all instead.
-                    // Note that cut/copy menu entries are not available for passwords.
-                    // This is however useful to delete or paste to replace the entire content.
-                    Selection.setSelection((Spannable) mText, 0, mText.length());
-                } else {
-                    selectCurrentWord();
-                }
+                selectCurrentWord();
 
                 menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
                     setAlphabeticShortcut('a').
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 125b210..a2e9486 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -55,6 +55,7 @@
     void showInputMethodSubtypePickerFromClient(in IInputMethodClient client);
     void showInputMethodAndSubtypeEnablerFromClient(in IInputMethodClient client, String topId);
     void setInputMethod(in IBinder token, String id);
+    void setInputMethodAndSubtype(in IBinder token, String id, in InputMethodSubtype subtype);
     void hideMySoftInput(in IBinder token, int flags);
     void showMySoftInput(in IBinder token, int flags);
     void updateStatusIcon(in IBinder token, String packageName, int iconId);
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_0.png b/core/res/res/drawable-mdpi/stat_sys_battery_0.png
index 750e652..e089120 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_0.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_0.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_15.png b/core/res/res/drawable-mdpi/stat_sys_battery_15.png
index 0eb58e1..be04321 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_15.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_15.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png
index 957dab3..f8011c9 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim0.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png
index e6d7da0..499ced9 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim100.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png
index 957dab3..c921d6a 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim15.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png
index 5aba0bb..f882002 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim28.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png
index dc5fac6..e7d1069 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim43.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png
index 1233ed8..5e0af3d 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim57.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png
index 06d397b..fb99059 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim71.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png
index 1056faf..072f907 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_charge_anim85.png
Binary files differ
diff --git a/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png b/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png
index 44eb313..3984c46 100644
--- a/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png
+++ b/core/res/res/drawable-mdpi/stat_sys_battery_unknown.png
Binary files differ
diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml
index 3982ed9..e3ba634 100644
--- a/core/res/res/layout/alert_dialog.xml
+++ b/core/res/res/layout/alert_dialog.xml
@@ -81,7 +81,8 @@
             android:paddingTop="2dip"
             android:paddingBottom="12dip"
             android:paddingLeft="14dip"
-            android:paddingRight="10dip">
+            android:paddingRight="10dip"
+            android:overScrollMode="ifContentScrolls">
             <TextView android:id="@+id/message"
                 style="?android:attr/textAppearanceMedium"
                 android:layout_width="match_parent"
diff --git a/core/res/res/layout/preference_dialog_edittext.xml b/core/res/res/layout/preference_dialog_edittext.xml
index 691ee8c..40b9e69 100644
--- a/core/res/res/layout/preference_dialog_edittext.xml
+++ b/core/res/res/layout/preference_dialog_edittext.xml
@@ -19,7 +19,8 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_marginTop="48dp"
-    android:layout_marginBottom="48dp">
+    android:layout_marginBottom="48dp"
+    android:overScrollMode="ifContentScrolls">
 
     <LinearLayout
         android:id="@+android:id/edittext_container"
diff --git a/core/res/res/layout/preference_list_content.xml b/core/res/res/layout/preference_list_content.xml
index 6c0e773..0f84418 100644
--- a/core/res/res/layout/preference_list_content.xml
+++ b/core/res/res/layout/preference_list_content.xml
@@ -56,7 +56,7 @@
 
         </LinearLayout>
 
-        <FrameLayout android:id="@+id/prefs"
+        <android.preference.PreferenceFrameLayout android:id="@+id/prefs"
                 android:layout_width="0px"
                 android:layout_height="match_parent"
                 android:layout_weight="20"
@@ -66,8 +66,6 @@
                 android:layout_marginBottom="16dp"
                 android:paddingLeft="32dip"
                 android:paddingRight="32dip"
-                android:paddingTop="48dip"
-                android:paddingBottom="48dip"
                 android:background="?attr/preferencePanelBackground"
                 android:visibility="gone" />
     </LinearLayout>
diff --git a/core/res/res/layout/preference_list_fragment.xml b/core/res/res/layout/preference_list_fragment.xml
index c499b8a..dbe0df0 100644
--- a/core/res/res/layout/preference_list_fragment.xml
+++ b/core/res/res/layout/preference_list_fragment.xml
@@ -18,6 +18,7 @@
 -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/default_preference_layout"
     android:orientation="vertical"
     android:layout_height="match_parent"
     android:layout_width="match_parent"
@@ -27,6 +28,9 @@
         android:layout_width="match_parent"
         android:layout_height="0px"
         android:layout_weight="1"
+        android:paddingTop="48dip"
+        android:paddingBottom="48dip"
+        android:clipToPadding="false"
         android:drawSelectorOnTop="false"
         android:cacheColorHint="@android:color/transparent"
         android:scrollbarAlwaysDrawVerticalTrack="true" />
diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml
index 94dcb6a..80d22f6 100644
--- a/core/res/res/layout/select_dialog.xml
+++ b/core/res/res/layout/select_dialog.xml
@@ -31,4 +31,5 @@
     android:layout_marginTop="5px"
     android:cacheColorHint="@null"
     android:divider="?android:attr/listDividerAlertDialog"
-    android:scrollbars="vertical" />
+    android:scrollbars="vertical"
+    android:overScrollMode="ifContentScrolls" />
diff --git a/core/res/res/values-xlarge/config.xml b/core/res/res/values-xlarge/config.xml
index 9504d04..37f59c8 100644
--- a/core/res/res/values-xlarge/config.xml
+++ b/core/res/res/values-xlarge/config.xml
@@ -30,8 +30,8 @@
     <!-- Enable lockscreen rotation -->
     <bool name="config_enableLockScreenRotation">true</bool>
 
-    <!-- Enables 3d task switcher on xlarge device -->
-    <bool name="config_enableRecentApps3D">true</bool>
+    <!-- see comment in values/config.xml -->
+    <integer name="config_longPressOnHomeBehavior">1</integer>
 
 </resources>
 
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 651bfea..3f81a89 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -610,6 +610,9 @@
 
         <!-- Specifies a drawable to use for the 'home as up' indicator. -->
         <attr name="homeAsUpIndicator" format="reference" />
+
+        <!-- Preference frame layout styles. -->
+        <attr name="preferenceFrameLayoutStyle" format="reference" />
     </declare-styleable>
 
     <!-- **************************************************************** -->
@@ -1632,6 +1635,20 @@
              <code>public void sayHello(View v)</code> method of your context
              (typically, your Activity). -->
         <attr name="onClick" format="string" />
+
+        <!-- Defines over-scrolling behavior. This property is used only if the
+             View is scrollable. Over-scrolling is the ability for the user to
+             receive feedback when attempting to scroll beyond meaningful content. -->
+        <attr name="overScrollMode">
+            <!-- Always show over-scroll effects, even if the content fits entirely
+                 within the available space. -->
+            <enum name="always" value="0" />
+            <!-- Only show over-scroll effects if the content is large
+                 enough to meaningfully scroll. -->
+            <enum name="ifContentScrolls" value="1" />
+            <!-- Never show over-scroll effects. -->
+            <enum name="never" value="2" />
+        </attr>
     </declare-styleable>
 
     <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any
@@ -2116,6 +2133,16 @@
         <!-- 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|color" />
+        <!-- Drawable to draw below list content. -->
+        <attr name="overScrollFooter" format="reference|color" />
+    </declare-styleable>
+    <declare-styleable name="PreferenceFrameLayout">
+        <!-- Padding to use at the top of the prefs content. -->
+        <attr name="topPadding" format="dimension" />
+        <!-- Padding to use at the bottom of the prefs content. -->
+        <attr name="bottomPadding" format="dimension" />
     </declare-styleable>
     <declare-styleable name="MenuView">
         <!-- Default appearance of menu item text. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 642a588..15b5db4 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -346,8 +346,12 @@
     <!-- Diable lockscreen rotation by default -->
     <bool name="config_enableLockScreenRotation">false</bool>
 
-    <!-- Enable 3D RecentApplications view -->
-    <bool name="config_enableRecentApps3D">false</bool>
+    <!-- Control the behavior when the user long presses the power button.
+            0 - Nothing
+            1 - Recent apps dialog
+            2 - Recent apps activity in SystemUI
+    -->
+    <integer name="config_longPressOnHomeBehavior">0</integer>
 
     <!-- Array of light sensor LUX values to define our levels for auto backlight brightness support.
          The N entries of this array define N + 1 zones as follows:
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index f4d1df8..f2ab5cd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -1253,6 +1253,9 @@
   <public type="attr" name="logo" id="0x010102be" />
   <public type="attr" name="xlargeScreens" id="0x010102bf" />
   <public type="attr" name="immersive" id="0x010102c0" />
+  <public type="attr" name="overScrollMode" id="0x010102c1" />
+  <public type="attr" name="overScrollHeader" id="0x010102c2" />
+  <public type="attr" name="overScrollFooter" id="0x010102c3" />
   <public type="attr" name="filterTouchesWhenObscured" id="0x010102c4" />
   <public type="attr" name="textSelectHandleLeft" id="0x010102c5" />
   <public type="attr" name="textSelectHandleRight" id="0x010102c6" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c8a5de8..0c3361d 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1824,8 +1824,10 @@
     <!-- Toast for double-tap -->
     <string name="double_tap_toast">Tip: double-tap to zoom in and out.</string>
 
-    <!-- Text to show in the auto complete drop down list on a text view when the browser can auto fill the entire form -->
+    <!-- Text to show in the auto complete drop down list on a text view when the WebView can auto fill the entire form, and the user has configured an AutoFill profile [CHAR-LIMIT=8] -->
     <string name="autofill_this_form">AutoFill</string>
+    <!-- Text to show in the auto complete drop down list on a text view when the WebView can auto fill the entire form but the user has not configured an AutoFill profile [CHAR-LIMIT=16] -->
+    <string name="setup_autofill">Setup AutoFill</string>
 
     <!-- String used to separate FirstName and LastName when writing out a local name
          e.g. John<separator>Smith [CHAR-LIMIT=NONE]-->
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 4aa16f9..6985de6 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -48,10 +48,15 @@
         <item name="bottomMedium">@android:drawable/popup_bottom_medium</item>
         <item name="centerMedium">@android:drawable/popup_center_medium</item>
     </style>
-    
+
+    <style name="Widget.PreferenceFrameLayout">
+        <item name="android:topPadding">0dip</item>
+        <item name="android:bottomPadding">0dip</item>
+    </style>
+
     <!-- Base style for animations.  This style specifies no animations. -->
     <style name="Animation" />
-    
+
     <!-- Standard animations for a full-screen window or activity. -->
     <style name="Animation.Activity">
         <item name="activityOpenEnterAnimation">@anim/activity_open_enter</item>
@@ -1874,4 +1879,8 @@
         <item name="android:textAppearance">@style/TextAppearance.Holo.Light.DialogWindowTitle</item>
     </style>
 
+    <style name="Widget.Holo.PreferenceFrameLayout">
+        <item name="android:topPadding">48dip</item>
+        <item name="android:bottomPadding">48dip</item>
+    </style>
 </resources>
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 9215bf2..1ef99d0 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -262,13 +262,16 @@
 
         <!-- SearchView attributes -->
         <item name="searchDropdownBackground">@android:drawable/search_dropdown_dark</item>
+
+        <!-- PreferenceFrameLayout attributes -->
+        <item name="preferenceFrameLayoutStyle">@android:style/Widget.PreferenceFrameLayout</item>
     </style>
-    
+
     <!-- Variant of the default (dark) theme with no title bar -->
     <style name="Theme.NoTitleBar">
         <item name="android:windowNoTitle">true</item>
     </style>
-    
+
     <!-- Variant of the default (dark) theme that has no title bar and
          fills the entire screen -->
     <style name="Theme.NoTitleBar.Fullscreen">
@@ -871,6 +874,9 @@
 
         <!-- SearchView attributes -->
         <item name="searchDropdownBackground">@android:drawable/search_dropdown_dark</item>
+
+        <!-- PreferenceFrameLayout attributes -->
+        <item name="preferenceFrameLayoutStyle">@android:style/Widget.Holo.PreferenceFrameLayout</item>
     </style>
 
     <!-- New Honeycomb holographic theme. Light version.  The widgets in the
diff --git a/core/tests/coretests/src/android/content/SyncOperationTest.java b/core/tests/coretests/src/android/content/SyncOperationTest.java
new file mode 100644
index 0000000..57435e5
--- /dev/null
+++ b/core/tests/coretests/src/android/content/SyncOperationTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+import android.accounts.Account;
+import android.os.Bundle;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * You can run those tests with:
+ *
+ * adb shell am instrument
+ * -e debug false
+ * -w
+ * -e class android.content.SyncOperationTest com.android.frameworks.coretests/android.test.InstrumentationTestRunner
+ */
+
+public class SyncOperationTest extends AndroidTestCase {
+
+    @SmallTest
+    public void testToKey() {
+        Account account1 = new Account("account1", "type1");
+        Account account2 = new Account("account2", "type2");
+
+        Bundle b1 = new Bundle();
+        Bundle b2 = new Bundle();
+        b2.putBoolean("b2", true);
+
+        SyncOperation op1 = new SyncOperation(account1,
+                1,
+                "authority1",
+                b1,
+                100,
+                1000,
+                10000);
+
+        // Same as op1 but different time infos
+        SyncOperation op2 = new SyncOperation(account1,
+                1,
+                "authority1",
+                b1,
+                200,
+                2000,
+                20000);
+
+        // Same as op1 but different authority
+        SyncOperation op3 = new SyncOperation(account1,
+                1,
+                "authority2",
+                b1,
+                100,
+                1000,
+                10000);
+
+        // Same as op1 but different account
+        SyncOperation op4 = new SyncOperation(account2,
+                1,
+                "authority1",
+                b1,
+                100,
+                1000,
+                10000);
+
+        // Same as op1 but different bundle
+        SyncOperation op5 = new SyncOperation(account1,
+                1,
+                "authority1",
+                b2,
+                100,
+                1000,
+                10000);
+
+        assertEquals(op1.key, op2.key);
+        assertNotSame(op1.key, op3.key);
+        assertNotSame(op1.key, op4.key);
+        assertNotSame(op1.key, op5.key);
+    }
+}
diff --git a/data/keyboards/Android.mk b/data/keyboards/Android.mk
new file mode 100644
index 0000000..8cba52d
--- /dev/null
+++ b/data/keyboards/Android.mk
@@ -0,0 +1,19 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This makefile performs build time validation of framework keymap files.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(LOCAL_PATH)/common.mk
diff --git a/data/keyboards/Generic.kcm b/data/keyboards/Generic.kcm
index 682584c..24b485d 100644
--- a/data/keyboards/Generic.kcm
+++ b/data/keyboards/Generic.kcm
@@ -22,6 +22,8 @@
 
 type FULL
 
+### Basic QWERTY keys ###
+
 key A {
     label:                              'A'
     base:                               'a'
@@ -369,6 +371,8 @@
     ctrl, alt, meta:                    none
 }
 
+### Numeric keypad ###
+
 key NUMPAD_0 {
     label, number:                      '0'
     base:                               fallback INSERT
@@ -499,3 +503,25 @@
     base:                               '\n' fallback ENTER
     ctrl, alt, meta:                    none fallback ENTER
 }
+
+### Special keys on phones ###
+
+key AT {
+    label, number:                      '@'
+    base:                               '@'
+}
+
+key STAR {
+    label, number:                      '*'
+    base:                               '*'
+}
+
+key POUND {
+    label, number:                      '#'
+    base:                               '#'
+}
+
+key PLUS {
+    label, number:                      '+'
+    base:                               '+'
+}
diff --git a/data/keyboards/Virtual.kcm b/data/keyboards/Virtual.kcm
index 9ada86a..8d3c7ac 100644
--- a/data/keyboards/Virtual.kcm
+++ b/data/keyboards/Virtual.kcm
@@ -19,6 +19,8 @@
 
 type FULL
 
+### Basic QWERTY keys ###
+
 key A {
     label:                              'A'
     base:                               'a'
@@ -366,6 +368,8 @@
     ctrl, alt, meta:                    none
 }
 
+### Numeric keypad ###
+
 key NUMPAD_0 {
     label, number:                      '0'
     base:                               fallback INSERT
@@ -496,3 +500,25 @@
     base:                               '\n' fallback ENTER
     ctrl, alt, meta:                    none fallback ENTER
 }
+
+### Special keys on phones ###
+
+key AT {
+    label, number:                      '@'
+    base:                               '@'
+}
+
+key STAR {
+    label, number:                      '*'
+    base:                               '*'
+}
+
+key POUND {
+    label, number:                      '#'
+    base:                               '#'
+}
+
+key PLUS {
+    label, number:                      '+'
+    base:                               '+'
+}
diff --git a/data/keyboards/common.mk b/data/keyboards/common.mk
new file mode 100644
index 0000000..3f05edb
--- /dev/null
+++ b/data/keyboards/common.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This is the list of framework provided keylayouts and key character maps to include.
+# Used by Android.mk and keyboards.mk.
+
+keylayouts := \
+    AVRCP.kl \
+    Generic.kl \
+    Motorola_Bluetooth_Wireless_Keyboard.kl \
+    qwerty.kl \
+    qwerty2.kl
+
+keycharmaps := \
+    Generic.kcm \
+    Virtual.kcm \
+    Motorola_Bluetooth_Wireless_Keyboard.kcm \
+    qwerty.kcm \
+    qwerty2.kcm
diff --git a/data/keyboards/keyboards.mk b/data/keyboards/keyboards.mk
index 3a0a553..b32e436 100644
--- a/data/keyboards/keyboards.mk
+++ b/data/keyboards/keyboards.mk
@@ -14,15 +14,7 @@
 
 # Warning: this is actually a product definition, to be inherited from
 
-keylayouts := \
-    AVRCP.kl \
-    Generic.kl \
-    Motorola_Bluetooth_Wireless_Keyboard.kl
-
-keycharmaps := \
-    Generic.kcm \
-    Virtual.kcm \
-    Motorola_Bluetooth_Wireless_Keyboard.kcm
+include $(LOCAL_PATH)/common.mk
 
 PRODUCT_COPY_FILES := $(foreach file,$(keylayouts),\
     frameworks/base/data/keyboards/$(file):system/usr/keylayout/$(file))
@@ -30,4 +22,4 @@
 PRODUCT_COPY_FILES += $(foreach file,$(keycharmaps),\
     frameworks/base/data/keyboards/$(file):system/usr/keychars/$(file))
 
-PRODUCT_PACKAGES := $(keycharmaps)
+PRODUCT_PACKAGES := $(keylayouts) $(keycharmaps)
diff --git a/data/keyboards/qwerty.kcm b/data/keyboards/qwerty.kcm
new file mode 100644
index 0000000..f31333e
--- /dev/null
+++ b/data/keyboards/qwerty.kcm
@@ -0,0 +1,508 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Emulator keyboard character map #1.
+#
+# This file is no longer used as the platform's default keyboard character map.
+# Refer to Generic.kcm and Virtual.kcm instead.
+#
+
+type ALPHA
+
+key A {
+    label:                              'A'
+    number:                             '2'
+    base:                               'a'
+    shift, capslock:                    'A'
+    alt:                                '#'
+    shift+alt, capslock+alt:            none
+}
+
+key B {
+    label:                              'B'
+    number:                             '2'
+    base:                               'b'
+    shift, capslock:                    'B'
+    alt:                                '<'
+    shift+alt, capslock+alt:            none
+}
+
+key C {
+    label:                              'C'
+    number:                             '2'
+    base:                               'c'
+    shift, capslock:                    'C'
+    alt:                                '9'
+    shift+alt, capslock+alt:            '\u00e7'
+}
+
+key D {
+    label:                              'D'
+    number:                             '3'
+    base:                               'd'
+    shift, capslock:                    'D'
+    alt:                                '5'
+    shift+alt, capslock+alt:            none
+}
+
+key E {
+    label:                              'E'
+    number:                             '3'
+    base:                               'e'
+    shift, capslock:                    'E'
+    alt:                                '2'
+    shift+alt, capslock+alt:            '\u0301'
+}
+
+key F {
+    label:                              'F'
+    number:                             '3'
+    base:                               'f'
+    shift, capslock:                    'F'
+    alt:                                '6'
+    shift+alt, capslock+alt:            '\u00a5'
+}
+
+key G {
+    label:                              'G'
+    number:                             '4'
+    base:                               'g'
+    shift, capslock:                    'G'
+    alt:                                '-'
+    shift+alt, capslock+alt:            '_'
+}
+
+key H {
+    label:                              'H'
+    number:                             '4'
+    base:                               'h'
+    shift, capslock:                    'H'
+    alt:                                '['
+    shift+alt, capslock+alt:            '{'
+}
+
+key I {
+    label:                              'I'
+    number:                             '4'
+    base:                               'i'
+    shift, capslock:                    'I'
+    alt:                                '$'
+    shift+alt, capslock+alt:            '\u0302'
+}
+
+key J {
+    label:                              'J'
+    number:                             '5'
+    base:                               'j'
+    shift, capslock:                    'J'
+    alt:                                ']'
+    shift+alt, capslock+alt:            '}'
+}
+
+key K {
+    label:                              'K'
+    number:                             '5'
+    base:                               'k'
+    shift, capslock:                    'K'
+    alt:                                '"'
+    shift+alt, capslock+alt:            '~'
+}
+
+key L {
+    label:                              'L'
+    number:                             '5'
+    base:                               'l'
+    shift, capslock:                    'L'
+    alt:                                '\''
+    shift+alt, capslock+alt:            '`'
+}
+
+key M {
+    label:                              'M'
+    number:                             '6'
+    base:                               'm'
+    shift, capslock:                    'M'
+    alt:                                '!'
+    shift+alt, capslock+alt:            none
+}
+
+key N {
+    label:                              'N'
+    number:                             '6'
+    base:                               'n'
+    shift, capslock:                    'N'
+    alt:                                '>'
+    shift+alt, capslock+alt:            '\u0303'
+}
+
+key O {
+    label:                              'O'
+    number:                             '6'
+    base:                               'o'
+    shift, capslock:                    'O'
+    alt:                                '('
+    shift+alt, capslock+alt:            none
+}
+
+key P {
+    label:                              'P'
+    number:                             '7'
+    base:                               'p'
+    shift, capslock:                    'P'
+    alt:                                ')'
+    shift+alt, capslock+alt:            none
+}
+
+key Q {
+    label:                              'Q'
+    number:                             '7'
+    base:                               'q'
+    shift, capslock:                    'Q'
+    alt:                                '*'
+    shift+alt, capslock+alt:            '\u0300'
+}
+
+key R {
+    label:                              'R'
+    number:                             '7'
+    base:                               'r'
+    shift, capslock:                    'R'
+    alt:                                '3'
+    shift+alt, capslock+alt:            '\u20ac'
+}
+
+key S {
+    label:                              'S'
+    number:                             '7'
+    base:                               's'
+    shift, capslock:                    'S'
+    alt:                                '4'
+    shift+alt, capslock+alt:            '\u00df'
+}
+
+key T {
+    label:                              'T'
+    number:                             '8'
+    base:                               't'
+    shift, capslock:                    'T'
+    alt:                                '+'
+    shift+alt, capslock+alt:            '\u00a3'
+}
+
+key U {
+    label:                              'U'
+    number:                             '8'
+    base:                               'u'
+    shift, capslock:                    'U'
+    alt:                                '&'
+    shift+alt, capslock+alt:            '\u0308'
+}
+
+key V {
+    label:                              'V'
+    number:                             '8'
+    base:                               'v'
+    shift, capslock:                    'V'
+    alt:                                '='
+    shift+alt, capslock+alt:            '^'
+}
+
+key W {
+    label:                              'W'
+    number:                             '9'
+    base:                               'w'
+    shift, capslock:                    'W'
+    alt:                                '1'
+    shift+alt, capslock+alt:            none
+}
+
+key X {
+    label:                              'X'
+    number:                             '9'
+    base:                               'x'
+    shift, capslock:                    'X'
+    alt:                                '8'
+    shift+alt, capslock+alt:            '\uef00'
+}
+
+key Y {
+    label:                              'Y'
+    number:                             '9'
+    base:                               'y'
+    shift, capslock:                    'Y'
+    alt:                                '%'
+    shift+alt, capslock+alt:            '\u00a1'
+}
+
+key Z {
+    label:                              'Z'
+    number:                             '9'
+    base:                               'z'
+    shift, capslock:                    'Z'
+    alt:                                '7'
+    shift+alt, capslock+alt:            none
+}
+
+key COMMA {
+    label:                              ','
+    number:                             ','
+    base:                               ','
+    shift, capslock:                    ';'
+    alt:                                ';'
+    shift+alt, capslock+alt:            '|'
+}
+
+key PERIOD {
+    label:                              '.'
+    number:                             '.'
+    base:                               '.'
+    shift:                              ':'
+    alt:                                ':'
+    shift+alt:                          '\u2026'
+}
+
+key AT {
+    label:                              '@'
+    number:                             '0'
+    base:                               '@'
+    shift:                              '0'
+    alt:                                '0'
+    shift+alt:                          '\u2022'
+}
+
+key SLASH {
+    label:                              '/'
+    number:                             '/'
+    base:                               '/'
+    shift:                              '?'
+    alt:                                '?'
+    shift+alt:                          '\\'
+}
+
+key SPACE {
+    label:                              ' '
+    number:                             ' '
+    base:                               ' '
+    shift:                              ' '
+    alt:                                '\uef01'
+    shift+alt:                          '\uef01'
+}
+
+key ENTER {
+    label:                              '\n'
+    number:                             '\n'
+    base:                               '\n'
+    shift:                              '\n'
+    alt:                                '\n'
+    shift+alt:                          '\n'
+}
+
+key TAB {
+    label:                              '\t'
+    number:                             '\t'
+    base:                               '\t'
+    shift:                              '\t'
+    alt:                                '\t'
+    shift+alt:                          '\t'
+}
+
+key 0 {
+    label:                              '0'
+    number:                             '0'
+    base:                               '0'
+    shift:                              ')'
+    alt:                                ')'
+    shift+alt:                          ')'
+}
+
+key 1 {
+    label:                              '1'
+    number:                             '1'
+    base:                               '1'
+    shift:                              '!'
+    alt:                                '!'
+    shift+alt:                          '!'
+}
+
+key 2 {
+    label:                              '2'
+    number:                             '2'
+    base:                               '2'
+    shift:                              '@'
+    alt:                                '@'
+    shift+alt:                          '@'
+}
+
+key 3 {
+    label:                              '3'
+    number:                             '3'
+    base:                               '3'
+    shift:                              '#'
+    alt:                                '#'
+    shift+alt:                          '#'
+}
+
+key 4 {
+    label:                              '4'
+    number:                             '4'
+    base:                               '4'
+    shift:                              '$'
+    alt:                                '$'
+    shift+alt:                          '$'
+}
+
+key 5 {
+    label:                              '5'
+    number:                             '5'
+    base:                               '5'
+    shift:                              '%'
+    alt:                                '%'
+    shift+alt:                          '%'
+}
+
+key 6 {
+    label:                              '6'
+    number:                             '6'
+    base:                               '6'
+    shift:                              '^'
+    alt:                                '^'
+    shift+alt:                          '^'
+}
+
+key 7 {
+    label:                              '7'
+    number:                             '7'
+    base:                               '7'
+    shift:                              '&'
+    alt:                                '&'
+    shift+alt:                          '&'
+}
+
+key 8 {
+    label:                              '8'
+    number:                             '8'
+    base:                               '8'
+    shift:                              '*'
+    alt:                                '*'
+    shift+alt:                          '*'
+}
+
+key 9 {
+    label:                              '9'
+    number:                             '9'
+    base:                               '9'
+    shift:                              '('
+    alt:                                '('
+    shift+alt:                          '('
+}
+
+key GRAVE {
+    label:                              '`'
+    number:                             '`'
+    base:                               '`'
+    shift:                              '~'
+    alt:                                '`'
+    shift+alt:                          '~'
+}
+
+key MINUS {
+    label:                              '-'
+    number:                             '-'
+    base:                               '-'
+    shift:                              '_'
+    alt:                                '-'
+    shift+alt:                          '_'
+}
+
+key EQUALS {
+    label:                              '='
+    number:                             '='
+    base:                               '='
+    shift:                              '+'
+    alt:                                '='
+    shift+alt:                          '+'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    number:                             '['
+    base:                               '['
+    shift:                              '{'
+    alt:                                '['
+    shift+alt:                          '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    number:                             ']'
+    base:                               ']'
+    shift:                              '}'
+    alt:                                ']'
+    shift+alt:                          '}'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    number:                             '\\'
+    base:                               '\\'
+    shift:                              '|'
+    alt:                                '\\'
+    shift+alt:                          '|'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    number:                             ';'
+    base:                               ';'
+    shift:                              ':'
+    alt:                                ';'
+    shift+alt:                          ':'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    number:                             '\''
+    base:                               '\''
+    shift:                              '"'
+    alt:                                '\''
+    shift+alt:                          '"'
+}
+
+key STAR {
+    label:                              '*'
+    number:                             '*'
+    base:                               '*'
+    shift:                              '*'
+    alt:                                '*'
+    shift+alt:                          '*'
+}
+
+key POUND {
+    label:                              '#'
+    number:                             '#'
+    base:                               '#'
+    shift:                              '#'
+    alt:                                '#'
+    shift+alt:                          '#'
+}
+
+key PLUS {
+    label:                              '+'
+    number:                             '+'
+    base:                               '+'
+    shift:                              '+'
+    alt:                                '+'
+    shift+alt:                          '+'
+}
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
new file mode 100644
index 0000000..f1caacd
--- /dev/null
+++ b/data/keyboards/qwerty.kl
@@ -0,0 +1,112 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Emulator keyboard layout #1.
+#
+# This file is no longer used as the platform's default keyboard layout.
+# Refer to Generic.kl instead.
+#
+
+key 399   GRAVE
+key 2     1
+key 3     2
+key 4     3
+key 5     4
+key 6     5
+key 7     6
+key 8     7
+key 9     8
+key 10    9
+key 11    0
+key 158   BACK              WAKE_DROPPED
+key 230   SOFT_RIGHT        WAKE
+key 60    SOFT_RIGHT        WAKE
+key 107   ENDCALL           WAKE_DROPPED
+key 62    ENDCALL           WAKE_DROPPED
+key 229   MENU              WAKE_DROPPED
+key 139   MENU              WAKE_DROPPED
+key 59    MENU              WAKE_DROPPED
+key 127   SEARCH            WAKE_DROPPED
+key 217   SEARCH            WAKE_DROPPED
+key 228   POUND
+key 227   STAR
+key 231   CALL              WAKE_DROPPED
+key 61    CALL              WAKE_DROPPED
+key 232   DPAD_CENTER       WAKE_DROPPED
+key 108   DPAD_DOWN         WAKE_DROPPED
+key 103   DPAD_UP           WAKE_DROPPED
+key 102   HOME              WAKE
+key 105   DPAD_LEFT         WAKE_DROPPED
+key 106   DPAD_RIGHT        WAKE_DROPPED
+key 115   VOLUME_UP         WAKE
+key 114   VOLUME_DOWN       WAKE
+key 116   POWER             WAKE
+key 212   CAMERA
+
+key 16    Q
+key 17    W
+key 18    E
+key 19    R
+key 20    T
+key 21    Y
+key 22    U
+key 23    I
+key 24    O
+key 25    P
+key 26    LEFT_BRACKET
+key 27    RIGHT_BRACKET
+key 43    BACKSLASH
+
+key 30    A
+key 31    S
+key 32    D
+key 33    F
+key 34    G
+key 35    H
+key 36    J
+key 37    K
+key 38    L
+key 39    SEMICOLON
+key 40    APOSTROPHE
+key 14    DEL
+        
+key 44    Z
+key 45    X
+key 46    C
+key 47    V
+key 48    B
+key 49    N
+key 50    M
+key 51    COMMA
+key 52    PERIOD
+key 53    SLASH
+key 28    ENTER
+        
+key 56    ALT_LEFT
+key 100   ALT_RIGHT
+key 42    SHIFT_LEFT
+key 54    SHIFT_RIGHT
+key 15    TAB
+key 57    SPACE
+key 150   EXPLORER
+key 155   ENVELOPE        
+
+key 12    MINUS
+key 13    EQUALS
+key 215   AT
+
+# On an AT keyboard: ESC, F10
+key 1     BACK              WAKE_DROPPED
+key 68    MENU              WAKE_DROPPED
diff --git a/data/keyboards/qwerty2.kcm b/data/keyboards/qwerty2.kcm
new file mode 100644
index 0000000..d96914f
--- /dev/null
+++ b/data/keyboards/qwerty2.kcm
@@ -0,0 +1,505 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Emulator keyboard character map #2.
+#
+
+type ALPHA
+
+key A {
+    label:                              'A'
+    number:                             '2'
+    base:                               'a'
+    shift, capslock:                    'A'
+    alt:                                'a'
+    shift+alt, capslock+alt:            'A'
+}
+
+key B {
+    label:                              'B'
+    number:                             '2'
+    base:                               'b'
+    shift, capslock:                    'B'
+    alt:                                'b'
+    shift+alt, capslock+alt:            'B'
+}
+
+key C {
+    label:                              'C'
+    number:                             '2'
+    base:                               'c'
+    shift, capslock:                    'C'
+    alt:                                '\u00e7'
+    shift+alt, capslock+alt:            '\u00e7'
+}
+
+key D {
+    label:                              'D'
+    number:                             '3'
+    base:                               'd'
+    shift, capslock:                    'D'
+    alt:                                '\''
+    shift+alt, capslock+alt:            '\''
+}
+
+key E {
+    label:                              'E'
+    number:                             '3'
+    base:                               'e'
+    shift, capslock:                    'E'
+    alt:                                '"'
+    shift+alt, capslock+alt:            '\u0301'
+}
+
+key F {
+    label:                              'F'
+    number:                             '3'
+    base:                               'f'
+    shift, capslock:                    'F'
+    alt:                                '['
+    shift+alt, capslock+alt:            '['
+}
+
+key G {
+    label:                              'G'
+    number:                             '4'
+    base:                               'g'
+    shift, capslock:                    'G'
+    alt:                                ']'
+    shift+alt, capslock+alt:            ']'
+}
+
+key H {
+    label:                              'H'
+    number:                             '4'
+    base:                               'h'
+    shift, capslock:                    'H'
+    alt:                                '<'
+    shift+alt, capslock+alt:            '<'
+}
+
+key I {
+    label:                              'I'
+    number:                             '4'
+    base:                               'i'
+    shift, capslock:                    'I'
+    alt:                                '-'
+    shift+alt, capslock+alt:            '\u0302'
+}
+
+key J {
+    label:                              'J'
+    number:                             '5'
+    base:                               'j'
+    shift, capslock:                    'J'
+    alt:                                '>'
+    shift+alt, capslock+alt:            '>'
+}
+
+key K {
+    label:                              'K'
+    number:                             '5'
+    base:                               'k'
+    shift, capslock:                    'K'
+    alt:                                ';'
+    shift+alt, capslock+alt:            '~'
+}
+
+key L {
+    label:                              'L'
+    number:                             '5'
+    base:                               'l'
+    shift, capslock:                    'L'
+    alt:                                ':'
+    shift+alt, capslock+alt:            '`'
+}
+
+key M {
+    label:                              'M'
+    number:                             '6'
+    base:                               'm'
+    shift, capslock:                    'M'
+    alt:                                '%'
+    shift+alt, capslock+alt:            none
+}
+
+key N {
+    label:                              'N'
+    number:                             '6'
+    base:                               'n'
+    shift, capslock:                    'N'
+    alt:                                none
+    shift+alt, capslock+alt:            '\u0303'
+}
+
+key O {
+    label:                              'O'
+    number:                             '6'
+    base:                               'o'
+    shift, capslock:                    'O'
+    alt:                                '+'
+    shift+alt, capslock+alt:            '+'
+}
+
+key P {
+    label:                              'P'
+    number:                             '7'
+    base:                               'p'
+    shift, capslock:                    'P'
+    alt:                                '='
+    shift+alt, capslock+alt:            '\u00a5'
+}
+
+key Q {
+    label:                              'Q'
+    number:                             '7'
+    base:                               'q'
+    shift, capslock:                    'Q'
+    alt:                                '|'
+    shift+alt, capslock+alt:            '\u0300'
+}
+
+key R {
+    label:                              'R'
+    number:                             '7'
+    base:                               'r'
+    shift, capslock:                    'R'
+    alt:                                '`'
+    shift+alt, capslock+alt:            '\u20ac'
+}
+
+key S {
+    label:                              'S'
+    number:                             '7'
+    base:                               's'
+    shift, capslock:                    'S'
+    alt:                                '\\'
+    shift+alt, capslock+alt:            '\u00df'
+}
+
+key T {
+    label:                              'T'
+    number:                             '8'
+    base:                               't'
+    shift, capslock:                    'T'
+    alt:                                '{'
+    shift+alt, capslock+alt:            '\u00a3'
+}
+
+key U {
+    label:                              'U'
+    number:                             '8'
+    base:                               'u'
+    shift, capslock:                    'U'
+    alt:                                '_'
+    shift+alt, capslock+alt:            '\u0308'
+}
+
+key V {
+    label:                              'V'
+    number:                             '8'
+    base:                               'v'
+    shift, capslock:                    'V'
+    alt:                                'v'
+    shift+alt, capslock+alt:            'V'
+}
+
+key W {
+    label:                              'W'
+    number:                             '9'
+    base:                               'w'
+    shift, capslock:                    'W'
+    alt:                                '~'
+    shift+alt, capslock+alt:            '~'
+}
+
+key X {
+    label:                              'X'
+    number:                             '9'
+    base:                               'x'
+    shift, capslock:                    'X'
+    alt:                                'x'
+    shift+alt, capslock+alt:            '\uef00'
+}
+
+key Y {
+    label:                              'Y'
+    number:                             '9'
+    base:                               'y'
+    shift, capslock:                    'Y'
+    alt:                                '}'
+    shift+alt, capslock+alt:            '\u00a1'
+}
+
+key Z {
+    label:                              'Z'
+    number:                             '9'
+    base:                               'z'
+    shift, capslock:                    'Z'
+    alt:                                'z'
+    shift+alt, capslock+alt:            'Z'
+}
+
+key COMMA {
+    label:                              ','
+    number:                             ','
+    base:                               ','
+    shift:                              '<'
+    alt:                                ','
+    shift+alt:                          ','
+}
+
+key PERIOD {
+    label:                              '.'
+    number:                             '.'
+    base:                               '.'
+    shift:                              '>'
+    alt:                                '.'
+    shift+alt:                          '\u2026'
+}
+
+key AT {
+    label:                              '@'
+    number:                             '@'
+    base:                               '@'
+    shift:                              '@'
+    alt:                                '@'
+    shift+alt:                          '\u2022'
+}
+
+key SLASH {
+    label:                              '/'
+    number:                             '/'
+    base:                               '/'
+    shift:                              '?'
+    alt:                                '?'
+    shift+alt:                          '?'
+}
+
+key SPACE {
+    label:                              ' '
+    number:                             ' '
+    base:                               ' '
+    shift:                              ' '
+    alt:                                '\uef01'
+    shift+alt:                          '\uef01'
+}
+
+key ENTER {
+    label:                              '\n'
+    number:                             '\n'
+    base:                               '\n'
+    shift:                              '\n'
+    alt:                                '\n'
+    shift+alt:                          '\n'
+}
+
+key TAB {
+    label:                              '\t'
+    number:                             '\t'
+    base:                               '\t'
+    shift:                              '\t'
+    alt:                                '\t'
+    shift+alt:                          '\t'
+}
+
+key 0 {
+    label:                              '0'
+    number:                             '0'
+    base:                               '0'
+    shift:                              ')'
+    alt:                                ')'
+    shift+alt:                          ')'
+}
+
+key 1 {
+    label:                              '1'
+    number:                             '1'
+    base:                               '1'
+    shift:                              '!'
+    alt:                                '!'
+    shift+alt:                          '!'
+}
+
+key 2 {
+    label:                              '2'
+    number:                             '2'
+    base:                               '2'
+    shift:                              '@'
+    alt:                                '@'
+    shift+alt:                          '@'
+}
+
+key 3 {
+    label:                              '3'
+    number:                             '3'
+    base:                               '3'
+    shift:                              '#'
+    alt:                                '#'
+    shift+alt:                          '#'
+}
+
+key 4 {
+    label:                              '4'
+    number:                             '4'
+    base:                               '4'
+    shift:                              '$'
+    alt:                                '$'
+    shift+alt:                          '$'
+}
+
+key 5 {
+    label:                              '5'
+    number:                             '5'
+    base:                               '5'
+    shift:                              '%'
+    alt:                                '%'
+    shift+alt:                          '%'
+}
+
+key 6 {
+    label:                              '6'
+    number:                             '6'
+    base:                               '6'
+    shift:                              '^'
+    alt:                                '^'
+    shift+alt:                          '^'
+}
+
+key 7 {
+    label:                              '7'
+    number:                             '7'
+    base:                               '7'
+    shift:                              '&'
+    alt:                                '&'
+    shift+alt:                          '&'
+}
+
+key 8 {
+    label:                              '8'
+    number:                             '8'
+    base:                               '8'
+    shift:                              '*'
+    alt:                                '*'
+    shift+alt:                          '*'
+}
+
+key 9 {
+    label:                              '9'
+    number:                             '9'
+    base:                               '9'
+    shift:                              '('
+    alt:                                '('
+    shift+alt:                          '('
+}
+
+key GRAVE {
+    label:                              '`'
+    number:                             '`'
+    base:                               '`'
+    shift:                              '~'
+    alt:                                '`'
+    shift+alt:                          '~'
+}
+
+key MINUS {
+    label:                              '-'
+    number:                             '-'
+    base:                               '-'
+    shift:                              '_'
+    alt:                                '-'
+    shift+alt:                          '_'
+}
+
+key EQUALS {
+    label:                              '='
+    number:                             '='
+    base:                               '='
+    shift:                              '+'
+    alt:                                '='
+    shift+alt:                          '+'
+}
+
+key LEFT_BRACKET {
+    label:                              '['
+    number:                             '['
+    base:                               '['
+    shift:                              '{'
+    alt:                                '['
+    shift+alt:                          '{'
+}
+
+key RIGHT_BRACKET {
+    label:                              ']'
+    number:                             ']'
+    base:                               ']'
+    shift:                              '}'
+    alt:                                ']'
+    shift+alt:                          '}'
+}
+
+key BACKSLASH {
+    label:                              '\\'
+    number:                             '\\'
+    base:                               '\\'
+    shift:                              '|'
+    alt:                                '\\'
+    shift+alt:                          '|'
+}
+
+key SEMICOLON {
+    label:                              ';'
+    number:                             ';'
+    base:                               ';'
+    shift:                              ':'
+    alt:                                ';'
+    shift+alt:                          ':'
+}
+
+key APOSTROPHE {
+    label:                              '\''
+    number:                             '\''
+    base:                               '\''
+    shift:                              '"'
+    alt:                                '\''
+    shift+alt:                          '"'
+}
+
+key STAR {
+    label:                              '*'
+    number:                             '*'
+    base:                               '*'
+    shift:                              '*'
+    alt:                                '*'
+    shift+alt:                          '*'
+}
+
+key POUND {
+    label:                              '#'
+    number:                             '#'
+    base:                               '#'
+    shift:                              '#'
+    alt:                                '#'
+    shift+alt:                          '#'
+}
+
+key PLUS {
+    label:                              '+'
+    number:                             '+'
+    base:                               '+'
+    shift:                              '+'
+    alt:                                '+'
+    shift+alt:                          '+'
+}
diff --git a/data/keyboards/qwerty2.kl b/data/keyboards/qwerty2.kl
new file mode 100644
index 0000000..863a258
--- /dev/null
+++ b/data/keyboards/qwerty2.kl
@@ -0,0 +1,109 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Emulator keyboard layout #2.
+#
+
+key 399   GRAVE
+key 2     1
+key 3     2
+key 4     3
+key 5     4
+key 6     5
+key 7     6
+key 8     7
+key 9     8
+key 10    9
+key 11    0
+key 158   BACK              WAKE_DROPPED
+key 230   SOFT_RIGHT        WAKE
+key 60    SOFT_RIGHT        WAKE
+key 107   ENDCALL           WAKE_DROPPED
+key 62    ENDCALL           WAKE_DROPPED
+key 229   MENU              WAKE_DROPPED
+key 139   MENU              WAKE_DROPPED
+key 59    MENU              WAKE_DROPPED
+key 127   SEARCH            WAKE_DROPPED
+key 217   SEARCH            WAKE_DROPPED
+key 228   POUND
+key 227   STAR
+key 231   CALL              WAKE_DROPPED
+key 61    CALL              WAKE_DROPPED
+key 232   DPAD_CENTER       WAKE_DROPPED
+key 108   DPAD_DOWN         WAKE_DROPPED
+key 103   DPAD_UP           WAKE_DROPPED
+key 102   HOME              WAKE
+key 105   DPAD_LEFT         WAKE_DROPPED
+key 106   DPAD_RIGHT        WAKE_DROPPED
+key 115   VOLUME_UP         WAKE
+key 114   VOLUME_DOWN       WAKE
+key 116   POWER             WAKE
+key 212   CAMERA
+
+key 16    Q
+key 17    W
+key 18    E
+key 19    R
+key 20    T
+key 21    Y
+key 22    U
+key 23    I
+key 24    O
+key 25    P
+key 26    LEFT_BRACKET
+key 27    RIGHT_BRACKET
+key 43    BACKSLASH
+
+key 30    A
+key 31    S
+key 32    D
+key 33    F
+key 34    G
+key 35    H
+key 36    J
+key 37    K
+key 38    L
+key 39    SEMICOLON
+key 40    APOSTROPHE
+key 14    DEL
+        
+key 44    Z
+key 45    X
+key 46    C
+key 47    V
+key 48    B
+key 49    N
+key 50    M
+key 51    COMMA
+key 52    PERIOD
+key 53    SLASH
+key 28    ENTER
+        
+key 56    ALT_LEFT
+key 100   ALT_RIGHT
+key 42    SHIFT_LEFT
+key 54    SHIFT_RIGHT
+key 15    TAB
+key 57    SPACE
+key 150   EXPLORER
+key 155   ENVELOPE        
+
+key 12    MINUS
+key 13    EQUALS
+key 215   AT
+
+# On an AT keyboard: ESC, F10
+key 1     BACK              WAKE_DROPPED
+key 68    MENU              WAKE_DROPPED
diff --git a/docs/html/guide/topics/fragments/index.jd b/docs/html/guide/topics/fragments/index.jd
index ce10ef7..766146e 100644
--- a/docs/html/guide/topics/fragments/index.jd
+++ b/docs/html/guide/topics/fragments/index.jd
@@ -399,7 +399,7 @@
 Fragment newFragment = new MyFragment();
 FragmentTransaction ft = openFragmentTransaction();
 // Replace and add to back stack
-ft.replace(newFragment, R.id.myfragment);
+ft.replace(R.id.myfragment, newFragment);
 ft.addToBackStack(null);
 // Apply changes
 ft.commit();
diff --git a/graphics/java/android/graphics/ComposeShader.java b/graphics/java/android/graphics/ComposeShader.java
index 8d5c913..241ab17 100644
--- a/graphics/java/android/graphics/ComposeShader.java
+++ b/graphics/java/android/graphics/ComposeShader.java
@@ -30,7 +30,7 @@
 
     /** Create a new compose shader, given shaders A, B, and a combining mode.
         When the mode is applied, it will be given the result from shader A as its
-        "dst", and the result of from shader B as its "src".
+        "dst", and the result from shader B as its "src".
         @param shaderA  The colors from this shader are seen as the "dst" by the mode
         @param shaderB  The colors from this shader are seen as the "src" by the mode
         @param mode     The mode that combines the colors from the two shaders. If mode
@@ -53,7 +53,7 @@
 
     /** Create a new compose shader, given shaders A, B, and a combining PorterDuff mode.
         When the mode is applied, it will be given the result from shader A as its
-        "dst", and the result of from shader B as its "src".
+        "dst", and the result from shader B as its "src".
         @param shaderA  The colors from this shader are seen as the "dst" by the mode
         @param shaderB  The colors from this shader are seen as the "src" by the mode
         @param mode     The PorterDuff mode that combines the colors from the two shaders.
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 5ebd2c0..00de39b 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -111,11 +111,6 @@
         resource->setPixels(NULL, NULL);
         return;
     }
-    recycle((void*) resource);
-}
-
-void ResourceCache::recycle(void* resource) {
-    Mutex::Autolock _l(mLock);
     ResourceReference* ref = mCache->indexOfKey(resource) >= 0 ? mCache->valueFor(resource) : NULL;
     if (ref == NULL) {
         // Should not get here - shouldn't get a call to recycle if we're not yet tracking it
diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h
index b0abe2c..1bb4390 100644
--- a/libs/hwui/ResourceCache.h
+++ b/libs/hwui/ResourceCache.h
@@ -61,7 +61,6 @@
     void decrementRefcount(SkBitmap* resource);
     void decrementRefcount(SkiaShader* resource);
     void decrementRefcount(SkiaColorFilter* resource);
-    void recycle(void* resource);
     void recycle(SkBitmap* resource);
     void destructor(SkBitmap* resource);
     void destructor(SkiaShader* resource);
diff --git a/libs/ui/Android.mk b/libs/ui/Android.mk
index 61d8abd..5948e04 100644
--- a/libs/ui/Android.mk
+++ b/libs/ui/Android.mk
@@ -1,7 +1,46 @@
+# Copyright (C) 2010 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
 LOCAL_PATH:= $(call my-dir)
+
+# libui is partially built for the host (used by build time keymap validation tool)
+# These files are common to host and target builds.
+commonSources:= \
+	Input.cpp \
+	Keyboard.cpp \
+	KeyLayoutMap.cpp \
+	KeyCharacterMap.cpp \
+
+# For the host
+# =====================================================
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= $(commonSources)
+
+LOCAL_MODULE:= libui
+
+include $(BUILD_HOST_STATIC_LIBRARY)
+
+
+# For the device
+# =====================================================
+
 include $(CLEAR_VARS)
 
 LOCAL_SRC_FILES:= \
+	$(commonSources) \
 	EGLUtils.cpp \
 	EventHub.cpp \
 	EventRecurrence.cpp \
@@ -10,10 +49,6 @@
 	GraphicBufferAllocator.cpp \
 	GraphicBufferMapper.cpp \
 	GraphicLog.cpp \
-	Keyboard.cpp \
-	KeyLayoutMap.cpp \
-	KeyCharacterMap.cpp \
-	Input.cpp \
 	InputDispatcher.cpp \
 	InputManager.cpp \
 	InputReader.cpp \
diff --git a/libs/ui/KeyCharacterMap.cpp b/libs/ui/KeyCharacterMap.cpp
index 890cc3f..e689c4b 100644
--- a/libs/ui/KeyCharacterMap.cpp
+++ b/libs/ui/KeyCharacterMap.cpp
@@ -733,6 +733,7 @@
             }
 
             combinedMeta |= metaState;
+            start = cur + 1;
 
             if (ch == '\0') {
                 break;
diff --git a/libs/ui/Overlay.cpp b/libs/ui/Overlay.cpp
index 3aa8950..b082c53 100644
--- a/libs/ui/Overlay.cpp
+++ b/libs/ui/Overlay.cpp
@@ -96,7 +96,6 @@
 }
 
 void Overlay::destroy() {  
-    if (mStatus != NO_ERROR) return;
 
     // Must delete the objects in reverse creation order, thus the
     //  data side must be closed first and then the destroy send to
@@ -104,9 +103,15 @@
     if (mOverlayData) {
         overlay_data_close(mOverlayData);
         mOverlayData = NULL;
+    } else {
+        LOGD("Overlay::destroy mOverlayData is NULL");
     }
 
-    mOverlayRef->mOverlayChannel->destroy();
+    if (mOverlayRef != 0) {
+        mOverlayRef->mOverlayChannel->destroy();
+    } else {
+        LOGD("Overlay::destroy mOverlayRef is NULL");
+    }
 }
 
 status_t Overlay::getStatus() const {
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index e48e9e8..250ec44 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -25,11 +25,11 @@
 import android.net.Uri;
 import android.os.Environment;
 import android.os.RemoteException;
+import android.provider.MediaStore;
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Files;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.MediaColumns;
-import android.provider.Mtp;
 import android.util.Log;
 
 import java.io.File;
@@ -1023,7 +1023,7 @@
         Log.d(TAG, "sessionEnded");
         if (mDatabaseModified) {
             Log.d(TAG, "sending ACTION_MTP_SESSION_END");
-            mContext.sendBroadcast(new Intent(Mtp.ACTION_MTP_SESSION_END));
+            mContext.sendBroadcast(new Intent(MediaStore.ACTION_MTP_SESSION_END));
             mDatabaseModified = false;
         }
     }
diff --git a/media/java/android/media/MtpClient.java b/media/java/android/media/PtpClient.java
similarity index 90%
rename from media/java/android/media/MtpClient.java
rename to media/java/android/media/PtpClient.java
index dcb1983..2e40635 100644
--- a/media/java/android/media/MtpClient.java
+++ b/media/java/android/media/PtpClient.java
@@ -21,9 +21,9 @@
 /**
  * {@hide}
  */
-public class MtpClient {
+public class PtpClient {
 
-    private static final String TAG = "MtpClient";
+    private static final String TAG = "PtpClient";
 
     private final Listener mListener;
 
@@ -31,10 +31,10 @@
         System.loadLibrary("media_jni");
     }
 
-    public MtpClient(Listener listener) {
+    public PtpClient(Listener listener) {
         native_setup();
         if (listener == null) {
-            throw new NullPointerException("MtpClient: listener is null");
+            throw new NullPointerException("PtpClient: listener is null");
         }
         mListener = listener;
     }
@@ -75,10 +75,10 @@
     }
 
     public interface Listener {
-        // called when a new MTP device has been discovered
+        // called when a new PTP device has been discovered
         void deviceAdded(int id);
 
-        // called when an MTP device has been removed
+        // called when an PTP device has been removed
         void deviceRemoved(int id);
     }
 
diff --git a/media/java/android/media/MtpCursor.java b/media/java/android/media/PtpCursor.java
similarity index 75%
rename from media/java/android/media/MtpCursor.java
rename to media/java/android/media/PtpCursor.java
index daa3f4d..bb5b1ec 100644
--- a/media/java/android/media/MtpCursor.java
+++ b/media/java/android/media/PtpCursor.java
@@ -18,17 +18,17 @@
 
 import android.database.AbstractWindowedCursor;
 import android.database.CursorWindow;
-import android.provider.Mtp;
+import android.provider.Ptp;
 import android.util.Log;
 
 import java.util.HashMap;
 
 /**
-  * Cursor class for MTP content provider
+  * Cursor class for PTP content provider
   * @hide
   */
-public final class MtpCursor extends AbstractWindowedCursor {
-    static final String TAG = "MtpCursor";
+public final class PtpCursor extends AbstractWindowedCursor {
+    static final String TAG = "PtpCursor";
     static final int NO_COUNT = -1;
 
     /* constants for queryType */
@@ -49,10 +49,10 @@
     private int mCount = NO_COUNT;
 
 
-    public MtpCursor(MtpClient client, int queryType, int deviceID, long storageID, long objectID,
+    public PtpCursor(PtpClient client, int queryType, int deviceID, long storageID, long objectID,
             String[] projection) {
         if (client == null) {
-            throw new NullPointerException("client null in MtpCursor constructor");
+            throw new NullPointerException("client null in PtpCursor constructor");
         }
         mColumns = projection;
 
@@ -132,19 +132,19 @@
     }
 
     /* Device Column IDs */
-     /* These must match the values in MtpCursor.cpp */
+     /* These must match the values in PtpCursor.cpp */
    private static final int DEVICE_ROW_ID          = 1;
     private static final int DEVICE_MANUFACTURER    = 2;
     private static final int DEVICE_MODEL           = 3;
 
     /* Storage Column IDs */
-    /* These must match the values in MtpCursor.cpp */
+    /* These must match the values in PtpCursor.cpp */
     private static final int STORAGE_ROW_ID         = 101;
     private static final int STORAGE_IDENTIFIER     = 102;
     private static final int STORAGE_DESCRIPTION    = 103;
 
     /* Object Column IDs */
-    /* These must match the values in MtpCursor.cpp */
+    /* These must match the values in PtpCursor.cpp */
     private static final int OBJECT_ROW_ID              = 201;
     private static final int OBJECT_STORAGE_ID          = 202;
     private static final int OBJECT_FORMAT              = 203;
@@ -173,45 +173,45 @@
 
     static {
         sDeviceProjectionMap = new HashMap<String, Integer>();
-        sDeviceProjectionMap.put(Mtp.Device._ID, new Integer(DEVICE_ROW_ID));
-        sDeviceProjectionMap.put(Mtp.Device.MANUFACTURER, new Integer(DEVICE_MANUFACTURER));
-        sDeviceProjectionMap.put(Mtp.Device.MODEL, new Integer(DEVICE_MODEL));
+        sDeviceProjectionMap.put(Ptp.Device._ID, new Integer(DEVICE_ROW_ID));
+        sDeviceProjectionMap.put(Ptp.Device.MANUFACTURER, new Integer(DEVICE_MANUFACTURER));
+        sDeviceProjectionMap.put(Ptp.Device.MODEL, new Integer(DEVICE_MODEL));
 
         sStorageProjectionMap = new HashMap<String, Integer>();
-        sStorageProjectionMap.put(Mtp.Storage._ID, new Integer(STORAGE_ROW_ID));
-        sStorageProjectionMap.put(Mtp.Storage.IDENTIFIER, new Integer(STORAGE_IDENTIFIER));
-        sStorageProjectionMap.put(Mtp.Storage.DESCRIPTION, new Integer(STORAGE_DESCRIPTION));
+        sStorageProjectionMap.put(Ptp.Storage._ID, new Integer(STORAGE_ROW_ID));
+        sStorageProjectionMap.put(Ptp.Storage.IDENTIFIER, new Integer(STORAGE_IDENTIFIER));
+        sStorageProjectionMap.put(Ptp.Storage.DESCRIPTION, new Integer(STORAGE_DESCRIPTION));
 
         sObjectProjectionMap = new HashMap<String, Integer>();
-        sObjectProjectionMap.put(Mtp.Object._ID, new Integer(OBJECT_ROW_ID));
-        sObjectProjectionMap.put(Mtp.Object.STORAGE_ID, new Integer(OBJECT_STORAGE_ID));
-        sObjectProjectionMap.put(Mtp.Object.FORMAT, new Integer(OBJECT_FORMAT));
-        sObjectProjectionMap.put(Mtp.Object.PROTECTION_STATUS, new Integer(OBJECT_PROTECTION_STATUS));
-        sObjectProjectionMap.put(Mtp.Object.SIZE, new Integer(OBJECT_SIZE));
-        sObjectProjectionMap.put(Mtp.Object.THUMB_FORMAT, new Integer(OBJECT_THUMB_FORMAT));
-        sObjectProjectionMap.put(Mtp.Object.THUMB_SIZE, new Integer(OBJECT_THUMB_SIZE));
-        sObjectProjectionMap.put(Mtp.Object.THUMB_WIDTH, new Integer(OBJECT_THUMB_WIDTH));
-        sObjectProjectionMap.put(Mtp.Object.THUMB_HEIGHT, new Integer(OBJECT_THUMB_HEIGHT));
-        sObjectProjectionMap.put(Mtp.Object.IMAGE_WIDTH, new Integer(OBJECT_IMAGE_WIDTH));
-        sObjectProjectionMap.put(Mtp.Object.IMAGE_HEIGHT, new Integer(OBJECT_IMAGE_HEIGHT));
-        sObjectProjectionMap.put(Mtp.Object.IMAGE_DEPTH, new Integer(OBJECT_IMAGE_DEPTH));
-        sObjectProjectionMap.put(Mtp.Object.PARENT, new Integer(OBJECT_PARENT));
-        sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_TYPE, new Integer(OBJECT_ASSOCIATION_TYPE));
-        sObjectProjectionMap.put(Mtp.Object.ASSOCIATION_DESC, new Integer(OBJECT_ASSOCIATION_DESC));
-        sObjectProjectionMap.put(Mtp.Object.SEQUENCE_NUMBER, new Integer(OBJECT_SEQUENCE_NUMBER));
-        sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME));
-        sObjectProjectionMap.put(Mtp.Object.DATE_CREATED, new Integer(OBJECT_DATE_CREATED));
-        sObjectProjectionMap.put(Mtp.Object.DATE_MODIFIED, new Integer(OBJECT_DATE_MODIFIED));
-        sObjectProjectionMap.put(Mtp.Object.KEYWORDS, new Integer(OBJECT_KEYWORDS));
-        sObjectProjectionMap.put(Mtp.Object.THUMB, new Integer(OBJECT_THUMB));
+        sObjectProjectionMap.put(Ptp.Object._ID, new Integer(OBJECT_ROW_ID));
+        sObjectProjectionMap.put(Ptp.Object.STORAGE_ID, new Integer(OBJECT_STORAGE_ID));
+        sObjectProjectionMap.put(Ptp.Object.FORMAT, new Integer(OBJECT_FORMAT));
+        sObjectProjectionMap.put(Ptp.Object.PROTECTION_STATUS, new Integer(OBJECT_PROTECTION_STATUS));
+        sObjectProjectionMap.put(Ptp.Object.SIZE, new Integer(OBJECT_SIZE));
+        sObjectProjectionMap.put(Ptp.Object.THUMB_FORMAT, new Integer(OBJECT_THUMB_FORMAT));
+        sObjectProjectionMap.put(Ptp.Object.THUMB_SIZE, new Integer(OBJECT_THUMB_SIZE));
+        sObjectProjectionMap.put(Ptp.Object.THUMB_WIDTH, new Integer(OBJECT_THUMB_WIDTH));
+        sObjectProjectionMap.put(Ptp.Object.THUMB_HEIGHT, new Integer(OBJECT_THUMB_HEIGHT));
+        sObjectProjectionMap.put(Ptp.Object.IMAGE_WIDTH, new Integer(OBJECT_IMAGE_WIDTH));
+        sObjectProjectionMap.put(Ptp.Object.IMAGE_HEIGHT, new Integer(OBJECT_IMAGE_HEIGHT));
+        sObjectProjectionMap.put(Ptp.Object.IMAGE_DEPTH, new Integer(OBJECT_IMAGE_DEPTH));
+        sObjectProjectionMap.put(Ptp.Object.PARENT, new Integer(OBJECT_PARENT));
+        sObjectProjectionMap.put(Ptp.Object.ASSOCIATION_TYPE, new Integer(OBJECT_ASSOCIATION_TYPE));
+        sObjectProjectionMap.put(Ptp.Object.ASSOCIATION_DESC, new Integer(OBJECT_ASSOCIATION_DESC));
+        sObjectProjectionMap.put(Ptp.Object.SEQUENCE_NUMBER, new Integer(OBJECT_SEQUENCE_NUMBER));
+        sObjectProjectionMap.put(Ptp.Object.NAME, new Integer(OBJECT_NAME));
+        sObjectProjectionMap.put(Ptp.Object.DATE_CREATED, new Integer(OBJECT_DATE_CREATED));
+        sObjectProjectionMap.put(Ptp.Object.DATE_MODIFIED, new Integer(OBJECT_DATE_MODIFIED));
+        sObjectProjectionMap.put(Ptp.Object.KEYWORDS, new Integer(OBJECT_KEYWORDS));
+        sObjectProjectionMap.put(Ptp.Object.THUMB, new Integer(OBJECT_THUMB));
 
-        sObjectProjectionMap.put(Mtp.Object.NAME, new Integer(OBJECT_NAME));
+        sObjectProjectionMap.put(Ptp.Object.NAME, new Integer(OBJECT_NAME));
     }
 
     // used by the JNI code
     private int mNativeContext;
 
-    private native final void native_setup(MtpClient client, int queryType,
+    private native final void native_setup(PtpClient client, int queryType,
             int deviceID, long storageID, long objectID, int[] columns);
     private native final void native_finalize();
     private native void native_wait_for_event();
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index 25d243b..fbdfa67 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -9,10 +9,10 @@
     android_media_ResampleInputStream.cpp \
     android_media_MediaProfiles.cpp \
     android_media_AmrInputStream.cpp \
-	android_media_MtpClient.cpp \
-	android_media_MtpCursor.cpp \
 	android_media_MtpDatabase.cpp \
 	android_media_MtpServer.cpp \
+	android_media_PtpClient.cpp \
+	android_media_PtpCursor.cpp \
 
 LOCAL_SHARED_LIBRARIES := \
     libandroid_runtime \
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 997d017..28aef0c 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -774,8 +774,8 @@
 extern int register_android_media_MediaScanner(JNIEnv *env);
 extern int register_android_media_ResampleInputStream(JNIEnv *env);
 extern int register_android_media_MediaProfiles(JNIEnv *env);
-extern int register_android_media_MtpClient(JNIEnv *env);
-extern int register_android_media_MtpCursor(JNIEnv *env);
+extern int register_android_media_PtpClient(JNIEnv *env);
+extern int register_android_media_PtpCursor(JNIEnv *env);
 extern int register_android_media_MtpDatabase(JNIEnv *env);
 extern int register_android_media_MtpServer(JNIEnv *env);
 extern int register_android_media_AmrInputStream(JNIEnv *env);
@@ -826,13 +826,13 @@
         goto bail;
     }
 
-    if (register_android_media_MtpClient(env) < 0) {
-        LOGE("ERROR: MtpClient native registration failed");
+    if (register_android_media_PtpClient(env) < 0) {
+        LOGE("ERROR: PtpClient native registration failed");
         goto bail;
     }
 
-    if (register_android_media_MtpCursor(env) < 0) {
-        LOGE("ERROR: MtpCursor native registration failed");
+    if (register_android_media_PtpCursor(env) < 0) {
+        LOGE("ERROR: PtpCursor native registration failed");
         goto bail;
     }
 
diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp
index 4525d1f..f04a2ae 100644
--- a/media/jni/android_media_MtpDatabase.cpp
+++ b/media/jni/android_media_MtpDatabase.cpp
@@ -948,18 +948,21 @@
             result = new MtpProperty(property, MTP_TYPE_UINT128);
             break;
         case MTP_PROPERTY_NAME:
-        case MTP_PROPERTY_DATE_MODIFIED:
         case MTP_PROPERTY_DISPLAY_NAME:
-        case MTP_PROPERTY_DATE_ADDED:
         case MTP_PROPERTY_ARTIST:
         case MTP_PROPERTY_ALBUM_NAME:
         case MTP_PROPERTY_ALBUM_ARTIST:
-        case MTP_PROPERTY_ORIGINAL_RELEASE_DATE:
         case MTP_PROPERTY_GENRE:
         case MTP_PROPERTY_COMPOSER:
         case MTP_PROPERTY_DESCRIPTION:
             result = new MtpProperty(property, MTP_TYPE_STR);
             break;
+        case MTP_PROPERTY_DATE_MODIFIED:
+        case MTP_PROPERTY_DATE_ADDED:
+        case MTP_PROPERTY_ORIGINAL_RELEASE_DATE:
+            result = new MtpProperty(property, MTP_TYPE_STR);
+            result->setFormDateTime();
+            break;
         case MTP_PROPERTY_OBJECT_FILE_NAME:
             // We allow renaming files and folders
             result = new MtpProperty(property, MTP_TYPE_STR, true);
diff --git a/media/jni/android_media_MtpClient.cpp b/media/jni/android_media_PtpClient.cpp
similarity index 80%
rename from media/jni/android_media_MtpClient.cpp
rename to media/jni/android_media_PtpClient.cpp
index 144dfc8..6af83e4 100644
--- a/media/jni/android_media_MtpClient.cpp
+++ b/media/jni/android_media_PtpClient.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MtpClientJNI"
+#define LOG_TAG "PtpClientJNI"
 #include "utils/Log.h"
 
 #include <stdio.h>
@@ -103,7 +103,7 @@
 // ----------------------------------------------------------------------------
 
 static void
-android_media_MtpClient_setup(JNIEnv *env, jobject thiz)
+android_media_PtpClient_setup(JNIEnv *env, jobject thiz)
 {
 #ifdef HAVE_ANDROID_OS
     LOGD("setup\n");
@@ -114,7 +114,7 @@
 }
 
 static void
-android_media_MtpClient_finalize(JNIEnv *env, jobject thiz)
+android_media_PtpClient_finalize(JNIEnv *env, jobject thiz)
 {
 #ifdef HAVE_ANDROID_OS
     LOGD("finalize\n");
@@ -126,7 +126,7 @@
 }
 
 static jboolean
-android_media_MtpClient_start(JNIEnv *env, jobject thiz)
+android_media_PtpClient_start(JNIEnv *env, jobject thiz)
 {
 #ifdef HAVE_ANDROID_OS
     LOGD("start\n");
@@ -138,7 +138,7 @@
 }
 
 static void
-android_media_MtpClient_stop(JNIEnv *env, jobject thiz)
+android_media_PtpClient_stop(JNIEnv *env, jobject thiz)
 {
 #ifdef HAVE_ANDROID_OS
     LOGD("stop\n");
@@ -148,7 +148,7 @@
 }
 
 static jboolean
-android_media_MtpClient_delete_object(JNIEnv *env, jobject thiz,
+android_media_PtpClient_delete_object(JNIEnv *env, jobject thiz,
         jint device_id, jlong object_id)
 {
 #ifdef HAVE_ANDROID_OS
@@ -162,7 +162,7 @@
 }
 
 static jlong
-android_media_MtpClient_get_parent(JNIEnv *env, jobject thiz,
+android_media_PtpClient_get_parent(JNIEnv *env, jobject thiz,
         jint device_id, jlong object_id)
 {
 #ifdef HAVE_ANDROID_OS
@@ -176,7 +176,7 @@
 }
 
 static jlong
-android_media_MtpClient_get_storage_id(JNIEnv *env, jobject thiz,
+android_media_PtpClient_get_storage_id(JNIEnv *env, jobject thiz,
         jint device_id, jlong object_id)
 {
  #ifdef HAVE_ANDROID_OS
@@ -190,7 +190,7 @@
 }
 
 static jboolean
-android_media_MtpClient_import_file(JNIEnv *env, jobject thiz,
+android_media_PtpClient_import_file(JNIEnv *env, jobject thiz,
         jint device_id, jlong object_id, jstring dest_path)
 {
 #ifdef HAVE_ANDROID_OS
@@ -209,28 +209,28 @@
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
-    {"native_setup",            "()V",  (void *)android_media_MtpClient_setup},
-    {"native_finalize",         "()V",  (void *)android_media_MtpClient_finalize},
-    {"native_start",            "()Z",  (void *)android_media_MtpClient_start},
-    {"native_stop",             "()V",  (void *)android_media_MtpClient_stop},
-    {"native_delete_object",   "(IJ)Z", (void *)android_media_MtpClient_delete_object},
-    {"native_get_parent",      "(IJ)J", (void *)android_media_MtpClient_get_parent},
-    {"native_get_storage_id",  "(IJ)J", (void *)android_media_MtpClient_get_storage_id},
+    {"native_setup",            "()V",  (void *)android_media_PtpClient_setup},
+    {"native_finalize",         "()V",  (void *)android_media_PtpClient_finalize},
+    {"native_start",            "()Z",  (void *)android_media_PtpClient_start},
+    {"native_stop",             "()V",  (void *)android_media_PtpClient_stop},
+    {"native_delete_object",   "(IJ)Z", (void *)android_media_PtpClient_delete_object},
+    {"native_get_parent",      "(IJ)J", (void *)android_media_PtpClient_get_parent},
+    {"native_get_storage_id",  "(IJ)J", (void *)android_media_PtpClient_get_storage_id},
     {"native_import_file",     "(IJLjava/lang/String;)Z",
-                                        (void *)android_media_MtpClient_import_file},
+                                        (void *)android_media_PtpClient_import_file},
 };
 
-static const char* const kClassPathName = "android/media/MtpClient";
+static const char* const kClassPathName = "android/media/PtpClient";
 
-int register_android_media_MtpClient(JNIEnv *env)
+int register_android_media_PtpClient(JNIEnv *env)
 {
     jclass clazz;
 
-    LOGD("register_android_media_MtpClient\n");
+    LOGD("register_android_media_PtpClient\n");
 
-    clazz = env->FindClass("android/media/MtpClient");
+    clazz = env->FindClass("android/media/PtpClient");
     if (clazz == NULL) {
-        LOGE("Can't find android/media/MtpClient");
+        LOGE("Can't find android/media/PtpClient");
         return -1;
     }
     method_deviceAdded = env->GetMethodID(clazz, "deviceAdded", "(I)V");
@@ -245,10 +245,10 @@
     }
     field_context = env->GetFieldID(clazz, "mNativeContext", "I");
     if (field_context == NULL) {
-        LOGE("Can't find MtpClient.mNativeContext");
+        LOGE("Can't find PtpClient.mNativeContext");
         return -1;
     }
 
     return AndroidRuntime::registerNativeMethods(env,
-                "android/media/MtpClient", gMethods, NELEM(gMethods));
+                "android/media/PtpClient", gMethods, NELEM(gMethods));
 }
diff --git a/media/jni/android_media_MtpCursor.cpp b/media/jni/android_media_PtpCursor.cpp
similarity index 72%
rename from media/jni/android_media_MtpCursor.cpp
rename to media/jni/android_media_PtpCursor.cpp
index 7a0ae8a..76c88f6 100644
--- a/media/jni/android_media_MtpCursor.cpp
+++ b/media/jni/android_media_PtpCursor.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MtpCursorJNI"
+#define LOG_TAG "PtpCursorJNI"
 #include "utils/Log.h"
 
 #include <stdio.h>
@@ -29,7 +29,7 @@
 #include "binder/CursorWindow.h"
 
 #include "MtpClient.h"
-#include "MtpCursor.h"
+#include "PtpCursor.h"
 
 using namespace android;
 
@@ -37,7 +37,7 @@
 
 static jfieldID field_context;
 
-// From android_media_MtpClient.cpp
+// From android_media_PtpClient.cpp
 MtpClient * get_client_from_object(JNIEnv * env, jobject javaClient);
 
 // ----------------------------------------------------------------------------
@@ -48,11 +48,11 @@
 }
 
 static void
-android_media_MtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient,
+android_media_PtpCursor_setup(JNIEnv *env, jobject thiz, jobject javaClient,
         jint queryType, jint deviceID, jlong storageID, jlong objectID, jintArray javaColumns)
 {
 #ifdef HAVE_ANDROID_OS
-    LOGD("android_media_MtpCursor_setup queryType: %d deviceID: %d storageID: %lld objectID: %lld\n",
+    LOGD("android_media_PtpCursor_setup queryType: %d deviceID: %d storageID: %lld objectID: %lld\n",
                 queryType, deviceID, storageID, objectID);
 
     int* columns = NULL;
@@ -63,7 +63,7 @@
     }
 
     MtpClient* client = get_client_from_object(env, javaClient);
-    MtpCursor* cursor = new MtpCursor(client, queryType,
+    PtpCursor* cursor = new PtpCursor(client, queryType,
             deviceID, storageID, objectID, columnCount, columns);
 
     if (columns)
@@ -73,17 +73,17 @@
 }
 
 static void
-android_media_MtpCursor_finalize(JNIEnv *env, jobject thiz)
+android_media_PtpCursor_finalize(JNIEnv *env, jobject thiz)
 {
 #ifdef HAVE_ANDROID_OS
     LOGD("finalize\n");
-    MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context);
+    PtpCursor *cursor = (PtpCursor *)env->GetIntField(thiz, field_context);
     delete cursor;
 #endif
 }
 
 static jint
-android_media_MtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindow, jint startPos)
+android_media_PtpCursor_fill_window(JNIEnv *env, jobject thiz, jobject javaWindow, jint startPos)
 {
 #ifdef HAVE_ANDROID_OS
     CursorWindow* window = get_window_from_object(env, javaWindow);
@@ -93,7 +93,7 @@
                           "Bad CursorWindow");
         return 0;
     }
-    MtpCursor *cursor = (MtpCursor *)env->GetIntField(thiz, field_context);
+    PtpCursor *cursor = (PtpCursor *)env->GetIntField(thiz, field_context);
 
     return cursor->fillWindow(window, startPos);
 #else
@@ -104,33 +104,33 @@
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
-    {"native_setup",            "(Landroid/media/MtpClient;IIJJ[I)V",
-                                        (void *)android_media_MtpCursor_setup},
-    {"native_finalize",         "()V",  (void *)android_media_MtpCursor_finalize},
+    {"native_setup",            "(Landroid/media/PtpClient;IIJJ[I)V",
+                                        (void *)android_media_PtpCursor_setup},
+    {"native_finalize",         "()V",  (void *)android_media_PtpCursor_finalize},
     {"native_fill_window",      "(Landroid/database/CursorWindow;I)I",
-                                        (void *)android_media_MtpCursor_fill_window},
+                                        (void *)android_media_PtpCursor_fill_window},
 
 };
 
-static const char* const kClassPathName = "android/media/MtpCursor";
+static const char* const kClassPathName = "android/media/PtpCursor";
 
-int register_android_media_MtpCursor(JNIEnv *env)
+int register_android_media_PtpCursor(JNIEnv *env)
 {
     jclass clazz;
 
-    LOGD("register_android_media_MtpCursor\n");
+    LOGD("register_android_media_PtpCursor\n");
 
-    clazz = env->FindClass("android/media/MtpCursor");
+    clazz = env->FindClass("android/media/PtpCursor");
     if (clazz == NULL) {
-        LOGE("Can't find android/media/MtpCursor");
+        LOGE("Can't find android/media/PtpCursor");
         return -1;
     }
     field_context = env->GetFieldID(clazz, "mNativeContext", "I");
     if (field_context == NULL) {
-        LOGE("Can't find MtpCursor.mNativeContext");
+        LOGE("Can't find PtpCursor.mNativeContext");
         return -1;
     }
 
     return AndroidRuntime::registerNativeMethods(env,
-                "android/media/MtpCursor", gMethods, NELEM(gMethods));
+                "android/media/PtpCursor", gMethods, NELEM(gMethods));
 }
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 3f32f2f..3108e4e 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -3898,32 +3898,34 @@
             mOutputFormat->setInt32(kKeyHeight, video_def->nFrameHeight);
             mOutputFormat->setInt32(kKeyColorFormat, video_def->eColorFormat);
 
-            OMX_CONFIG_RECTTYPE rect;
-            status_t err =
-                     mOMX->getConfig(
-                         mNode, OMX_IndexConfigCommonOutputCrop,
-                         &rect, sizeof(rect));
+            if (!mIsEncoder) {
+                OMX_CONFIG_RECTTYPE rect;
+                status_t err =
+                        mOMX->getConfig(
+                            mNode, OMX_IndexConfigCommonOutputCrop,
+                            &rect, sizeof(rect));
 
-            if (err == OK) {
-                CHECK_GE(rect.nLeft, 0);
-                CHECK_GE(rect.nTop, 0);
-                CHECK_GE(rect.nWidth, 0u);
-                CHECK_GE(rect.nHeight, 0u);
-                CHECK_LE(rect.nLeft + rect.nWidth - 1, video_def->nFrameWidth);
-                CHECK_LE(rect.nTop + rect.nHeight - 1, video_def->nFrameHeight);
+                if (err == OK) {
+                    CHECK_GE(rect.nLeft, 0);
+                    CHECK_GE(rect.nTop, 0);
+                    CHECK_GE(rect.nWidth, 0u);
+                    CHECK_GE(rect.nHeight, 0u);
+                    CHECK_LE(rect.nLeft + rect.nWidth - 1, video_def->nFrameWidth);
+                    CHECK_LE(rect.nTop + rect.nHeight - 1, video_def->nFrameHeight);
 
-                mOutputFormat->setRect(
-                        kKeyCropRect,
-                        rect.nLeft,
-                        rect.nTop,
-                        rect.nLeft + rect.nWidth - 1,
-                        rect.nTop + rect.nHeight - 1);
-            } else {
-                mOutputFormat->setRect(
-                        kKeyCropRect,
-                        0, 0,
-                        video_def->nFrameWidth - 1,
-                        video_def->nFrameHeight - 1);
+                    mOutputFormat->setRect(
+                            kKeyCropRect,
+                            rect.nLeft,
+                            rect.nTop,
+                            rect.nLeft + rect.nWidth - 1,
+                            rect.nTop + rect.nHeight - 1);
+                } else {
+                    mOutputFormat->setRect(
+                            kKeyCropRect,
+                            0, 0,
+                            video_def->nFrameWidth - 1,
+                            video_def->nFrameHeight - 1);
+                }
             }
 
             break;
diff --git a/media/mtp/Android.mk b/media/mtp/Android.mk
index 7502f6e..b7e1a2a 100644
--- a/media/mtp/Android.mk
+++ b/media/mtp/Android.mk
@@ -22,7 +22,6 @@
 
 LOCAL_SRC_FILES:=                                       \
                   MtpClient.cpp                         \
-                  MtpCursor.cpp                         \
                   MtpDataPacket.cpp                     \
                   MtpDebug.cpp                          \
                   MtpDevice.cpp                         \
@@ -38,6 +37,7 @@
                   MtpStringBuffer.cpp                   \
                   MtpStorage.cpp                        \
                   MtpUtils.cpp                          \
+                  PtpCursor.cpp                         \
 
 LOCAL_MODULE:= libmtp
 
@@ -47,32 +47,3 @@
 
 endif
 
-ifeq ($(HOST_OS),linux)
-
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:=                                       \
-                  MtpClient.cpp                         \
-                  MtpCursor.cpp                         \
-                  MtpDataPacket.cpp                     \
-                  MtpDebug.cpp                          \
-                  MtpDevice.cpp                         \
-                  MtpEventPacket.cpp                    \
-                  MtpDeviceInfo.cpp                     \
-                  MtpObjectInfo.cpp                     \
-                  MtpPacket.cpp                         \
-                  MtpProperty.cpp                       \
-                  MtpRequestPacket.cpp                  \
-                  MtpResponsePacket.cpp                 \
-                  MtpStorageInfo.cpp                    \
-                  MtpStringBuffer.cpp                   \
-                  MtpStorage.cpp                        \
-                  MtpUtils.cpp                          \
-
-LOCAL_MODULE:= libmtp
-
-LOCAL_CFLAGS := -DMTP_HOST
-
-include $(BUILD_HOST_STATIC_LIBRARY)
-
-endif
diff --git a/media/mtp/MtpPacket.cpp b/media/mtp/MtpPacket.cpp
index 42bf8ba..bd6196f 100644
--- a/media/mtp/MtpPacket.cpp
+++ b/media/mtp/MtpPacket.cpp
@@ -123,7 +123,7 @@
 
 uint32_t MtpPacket::getParameter(int index) const {
     if (index < 1 || index > 5) {
-        LOGE("index %d out of range in MtpRequestPacket::getParameter", index);
+        LOGE("index %d out of range in MtpPacket::getParameter", index);
         return 0;
     }
     return getUInt32(MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t));
@@ -131,7 +131,7 @@
 
 void MtpPacket::setParameter(int index, uint32_t value) {
     if (index < 1 || index > 5) {
-        LOGE("index %d out of range in MtpResponsePacket::setParameter", index);
+        LOGE("index %d out of range in MtpPacket::setParameter", index);
         return;
     }
     int offset = MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t);
diff --git a/media/mtp/MtpProperty.cpp b/media/mtp/MtpProperty.cpp
index 86889c3..42945f5 100644
--- a/media/mtp/MtpProperty.cpp
+++ b/media/mtp/MtpProperty.cpp
@@ -312,6 +312,10 @@
     }
 }
 
+void MtpProperty::setFormDateTime() {
+     mFormFlag = kFormDateTime;
+}
+
 void MtpProperty::print() {
     LOGV("MtpProperty %04X\n", mCode);
     LOGV("    type %04X\n", mType);
diff --git a/media/mtp/MtpProperty.h b/media/mtp/MtpProperty.h
index c12399c..f783a87 100644
--- a/media/mtp/MtpProperty.h
+++ b/media/mtp/MtpProperty.h
@@ -58,6 +58,7 @@
         kFormNone = 0,
         kFormRange = 1,
         kFormEnum = 2,
+        kFormDateTime = 3,
     };
 
     uint32_t            mGroupCode;
@@ -90,6 +91,7 @@
 
     void                setFormRange(int min, int max, int step);
     void                setFormEnum(const int* values, int count);
+    void                setFormDateTime();
 
     void                print();
 
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index ca13636..c3755f3 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -66,7 +66,7 @@
 //    MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
 //    MTP_OPERATION_MOVE_OBJECT,
 //    MTP_OPERATION_COPY_OBJECT,
-//    MTP_OPERATION_GET_PARTIAL_OBJECT,
+    MTP_OPERATION_GET_PARTIAL_OBJECT,
 //    MTP_OPERATION_INITIATE_OPEN_CAPTURE,
     MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
     MTP_OPERATION_GET_OBJECT_PROP_DESC,
@@ -289,6 +289,9 @@
         case MTP_OPERATION_GET_OBJECT:
             response = doGetObject();
             break;
+        case MTP_OPERATION_GET_PARTIAL_OBJECT:
+            response = doGetPartialObject();
+            break;
         case MTP_OPERATION_SEND_OBJECT_INFO:
             response = doSendObjectInfo();
             break;
@@ -536,7 +539,7 @@
     MtpObjectFormat format = mRequest.getParameter(2);
     MtpDeviceProperty property = mRequest.getParameter(3);
     int groupCode = mRequest.getParameter(4);
-    int depth = mRequest.getParameter(4);
+    int depth = mRequest.getParameter(5);
    LOGD("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n",
             handle, MtpDebug::getFormatCodeName(format),
             MtpDebug::getObjectPropCodeName(property), groupCode, depth);
@@ -583,6 +586,45 @@
     return MTP_RESPONSE_OK;
 }
 
+MtpResponseCode MtpServer::doGetPartialObject() {
+    MtpObjectHandle handle = mRequest.getParameter(1);
+    uint32_t offset = mRequest.getParameter(2);
+    uint32_t length = mRequest.getParameter(3);
+    MtpString pathBuf;
+    int64_t fileLength;
+    int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength);
+    if (result != MTP_RESPONSE_OK)
+        return result;
+    if (offset + length > fileLength)
+        length = fileLength - offset;
+
+    const char* filePath = (const char *)pathBuf;
+    mtp_file_range  mfr;
+    mfr.fd = open(filePath, O_RDONLY);
+    if (mfr.fd < 0) {
+        return MTP_RESPONSE_GENERAL_ERROR;
+    }
+    mfr.offset = offset;
+    mfr.length = length;
+    mResponse.setParameter(1, length);
+
+    // send data header
+    mData.setOperationCode(mRequest.getOperationCode());
+    mData.setTransactionID(mRequest.getTransactionID());
+    mData.writeDataHeader(mFD, length + MTP_CONTAINER_HEADER_SIZE);
+
+    // then transfer the file
+    int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
+    close(mfr.fd);
+    if (ret < 0) {
+        if (errno == ECANCELED)
+            return MTP_RESPONSE_TRANSACTION_CANCELLED;
+        else
+            return MTP_RESPONSE_GENERAL_ERROR;
+    }
+    return MTP_RESPONSE_OK;
+}
+
 MtpResponseCode MtpServer::doSendObjectInfo() {
     MtpString path;
     MtpStorageID storageID = mRequest.getParameter(1);
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index e65ddb0..5aee4ea 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -96,6 +96,7 @@
     MtpResponseCode     doGetObjectPropList();
     MtpResponseCode     doGetObjectInfo();
     MtpResponseCode     doGetObject();
+    MtpResponseCode     doGetPartialObject();
     MtpResponseCode     doSendObjectInfo();
     MtpResponseCode     doSendObject();
     MtpResponseCode     doDeleteObject();
diff --git a/media/mtp/MtpCursor.cpp b/media/mtp/PtpCursor.cpp
similarity index 91%
rename from media/mtp/MtpCursor.cpp
rename to media/mtp/PtpCursor.cpp
index 35d90dc..7294cef 100644
--- a/media/mtp/MtpCursor.cpp
+++ b/media/mtp/PtpCursor.cpp
@@ -14,11 +14,11 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MtpCursor"
+#define LOG_TAG "PtpCursor"
 
 #include "MtpDebug.h"
 #include "MtpClient.h"
-#include "MtpCursor.h"
+#include "PtpCursor.h"
 #include "MtpDevice.h"
 #include "MtpDeviceInfo.h"
 #include "MtpObjectInfo.h"
@@ -30,19 +30,19 @@
 namespace android {
 
 /* Device Column IDs */
-/* These must match the values in MtpCursor.java */
+/* These must match the values in PtpCursor.java */
 #define DEVICE_ROW_ID           1
 #define DEVICE_MANUFACTURER     2
 #define DEVICE_MODEL            3
 
 /* Storage Column IDs */
-/* These must match the values in MtpCursor.java */
+/* These must match the values in PtpCursor.java */
 #define STORAGE_ROW_ID          101
 #define STORAGE_IDENTIFIER      102
 #define STORAGE_DESCRIPTION     103
 
 /* Object Column IDs */
-/* These must match the values in MtpCursor.java */
+/* These must match the values in PtpCursor.java */
 #define OBJECT_ROW_ID               201
 #define OBJECT_STORAGE_ID           202
 #define OBJECT_FORMAT               203
@@ -65,7 +65,7 @@
 #define OBJECT_KEYWORDS             220
 #define OBJECT_THUMB                221
 
-MtpCursor::MtpCursor(MtpClient* client, int queryType, int deviceID,
+PtpCursor::PtpCursor(MtpClient* client, int queryType, int deviceID,
                 MtpStorageID storageID, MtpObjectHandle objectID,
                 int columnCount, int* columns)
         :   mClient(client),
@@ -82,12 +82,12 @@
     }
 }
 
-MtpCursor::~MtpCursor() {
+PtpCursor::~PtpCursor() {
     delete[] mColumns;
 }
 
-int MtpCursor::fillWindow(CursorWindow* window, int startPos) {
-    LOGD("MtpCursor::fillWindow mQueryType: %d\n", mQueryType);
+int PtpCursor::fillWindow(CursorWindow* window, int startPos) {
+    LOGD("PtpCursor::fillWindow mQueryType: %d\n", mQueryType);
 
     switch (mQueryType) {
         case DEVICE:
@@ -107,12 +107,12 @@
         case OBJECT_CHILDREN:
             return fillObjects(window, mQbjectID, startPos);
         default:
-            LOGE("MtpCursor::fillWindow: unknown query type %d\n", mQueryType);
+            LOGE("PtpCursor::fillWindow: unknown query type %d\n", mQueryType);
             return 0;
     }
 }
 
-int MtpCursor::fillDevices(CursorWindow* window, int startPos) {
+int PtpCursor::fillDevices(CursorWindow* window, int startPos) {
     int count = 0;
     MtpDeviceList& deviceList = mClient->getDeviceList();
     for (int i = 0; i < deviceList.size(); i++) {
@@ -127,7 +127,7 @@
     return count;
 }
 
-int MtpCursor::fillDevice(CursorWindow* window, int startPos) {
+int PtpCursor::fillDevice(CursorWindow* window, int startPos) {
     MtpDevice* device = mClient->getDevice(mDeviceID);
     if (device && fillDevice(window, device, startPos))
         return 1;
@@ -135,7 +135,7 @@
         return 0;
 }
 
-int MtpCursor::fillStorages(CursorWindow* window, int startPos) {
+int PtpCursor::fillStorages(CursorWindow* window, int startPos) {
     int count = 0;
     MtpDevice* device = mClient->getDevice(mDeviceID);
     if (!device)
@@ -157,7 +157,7 @@
     return count;
 }
 
-int MtpCursor::fillStorage(CursorWindow* window, int startPos) {
+int PtpCursor::fillStorage(CursorWindow* window, int startPos) {
     MtpDevice* device = mClient->getDevice(mDeviceID);
     if (device && fillStorage(window, device, mStorageID, startPos))
         return 1;
@@ -165,7 +165,7 @@
         return 0;
 }
 
-int MtpCursor::fillObjects(CursorWindow* window, int parent, int startPos) {
+int PtpCursor::fillObjects(CursorWindow* window, int parent, int startPos) {
     int count = 0;
     MtpDevice* device = mClient->getDevice(mDeviceID);
     if (!device)
@@ -187,7 +187,7 @@
     return count;
 }
 
-int MtpCursor::fillObject(CursorWindow* window, int startPos) {
+int PtpCursor::fillObject(CursorWindow* window, int startPos) {
     MtpDevice* device = mClient->getDevice(mDeviceID);
     if (device && fillObject(window, device, mQbjectID, startPos))
         return 1;
@@ -195,7 +195,7 @@
         return 0;
 }
 
-bool MtpCursor::fillDevice(CursorWindow* window, MtpDevice* device, int row) {
+bool PtpCursor::fillDevice(CursorWindow* window, MtpDevice* device, int row) {
     MtpDeviceInfo* deviceInfo = device->getDeviceInfo();
     if (!deviceInfo)
         return false;
@@ -225,7 +225,7 @@
     return true;
 }
 
-bool MtpCursor::fillStorage(CursorWindow* window, MtpDevice* device,
+bool PtpCursor::fillStorage(CursorWindow* window, MtpDevice* device,
         MtpStorageID storageID, int row) {
 
 LOGD("fillStorage %d\n", storageID);
@@ -273,7 +273,7 @@
     return false;
 }
 
-bool MtpCursor::fillObject(CursorWindow* window, MtpDevice* device,
+bool PtpCursor::fillObject(CursorWindow* window, MtpDevice* device,
         MtpObjectHandle objectID, int row) {
 
     MtpObjectInfo* objectInfo = device->getObjectInfo(objectID);
@@ -385,7 +385,7 @@
     return false;
 }
 
-bool MtpCursor::prepareRow(CursorWindow* window) {
+bool PtpCursor::prepareRow(CursorWindow* window) {
     if (!window->setNumColumns(mColumnCount)) {
         LOGE("Failed to change column count from %d to %d", window->getNumColumns(), mColumnCount);
         return false;
@@ -399,7 +399,7 @@
 }
 
 
-bool MtpCursor::putLong(CursorWindow* window, int64_t value, int row, int column) {
+bool PtpCursor::putLong(CursorWindow* window, int64_t value, int row, int column) {
     if (!window->putLong(row, column, value)) {
         window->freeLastRow();
         LOGE("Failed allocating space for a long in column %d", column);
@@ -408,7 +408,7 @@
     return true;
 }
 
-bool MtpCursor::putString(CursorWindow* window, const char* text, int row, int column) {
+bool PtpCursor::putString(CursorWindow* window, const char* text, int row, int column) {
     int size = strlen(text) + 1;
     int offset = window->alloc(size);
     if (!offset) {
@@ -427,7 +427,7 @@
     return true;
 }
 
-bool MtpCursor::putThumbnail(CursorWindow* window, MtpObjectHandle objectID,
+bool PtpCursor::putThumbnail(CursorWindow* window, MtpObjectHandle objectID,
                             MtpObjectFormat format, int row, int column) {
     MtpDevice* device = mClient->getDevice(mDeviceID);
     void* thumbnail;
diff --git a/media/mtp/MtpCursor.h b/media/mtp/PtpCursor.h
similarity index 93%
rename from media/mtp/MtpCursor.h
rename to media/mtp/PtpCursor.h
index 2e03c29..38a1d47 100644
--- a/media/mtp/MtpCursor.h
+++ b/media/mtp/PtpCursor.h
@@ -14,8 +14,8 @@
  * limitations under the License.
  */
 
-#ifndef _MTP_CURSOR_H
-#define _MTP_CURSOR_H
+#ifndef _PTP_CURSOR_H
+#define _PTP_CURSOR_H
 
 #include "MtpTypes.h"
 
@@ -23,7 +23,7 @@
 
 class CursorWindow;
 
-class MtpCursor {
+class PtpCursor {
 private:
     enum {
         DEVICE              = 1,
@@ -45,10 +45,10 @@
     int*            mColumns;
 
 public:
-                MtpCursor(MtpClient* client, int queryType, int deviceID,
+                PtpCursor(MtpClient* client, int queryType, int deviceID,
                         MtpStorageID storageID, MtpObjectHandle objectID,
                         int columnCount, int* columns);
-    virtual     ~MtpCursor();
+    virtual     ~PtpCursor();
 
     int         fillWindow(CursorWindow* window, int startPos);
 
@@ -75,4 +75,4 @@
 
 }; // namespace android
 
-#endif // _MTP_CURSOR_H
+#endif // _PTP_CURSOR_H
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java
index c04873a..0942d1f 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/CameraBrowser.java
@@ -24,7 +24,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
-import android.provider.Mtp;
+import android.provider.Ptp;
 import android.util.Log;
 import android.view.View;
 import android.widget.ListAdapter;
@@ -58,7 +58,7 @@
     }
 
     private static final String[] DEVICE_COLUMNS =
-         new String[] { Mtp.Device._ID, Mtp.Device.MANUFACTURER, Mtp.Device.MODEL };
+         new String[] { Ptp.Device._ID, Ptp.Device.MANUFACTURER, Ptp.Device.MODEL };
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -71,7 +71,7 @@
     protected void onResume() {
         super.onResume();
 
-        Cursor c = getContentResolver().query(Mtp.Device.CONTENT_URI,
+        Cursor c = getContentResolver().query(Ptp.Device.CONTENT_URI,
                 DEVICE_COLUMNS, null, null, null);
         Log.d(TAG, "query returned " + c);
         startManagingCursor(c);
@@ -80,12 +80,12 @@
         // Map Cursor columns to views defined in simple_list_item_2.xml
         mAdapter = new SimpleCursorAdapter(this,
                 android.R.layout.simple_list_item_2, c,
-                        new String[] { Mtp.Device.MANUFACTURER, Mtp.Device.MODEL },
+                        new String[] { Ptp.Device.MANUFACTURER, Ptp.Device.MODEL },
                         new int[] { android.R.id.text1, android.R.id.text2 });
         setListAdapter(mAdapter);
 
         // register for changes to the device list
-        mResolver.registerContentObserver(Mtp.Device.CONTENT_URI, true, mDeviceObserver);
+        mResolver.registerContentObserver(Ptp.Device.CONTENT_URI, true, mDeviceObserver);
     }
 
     @Override
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
index 2060657..40c5978 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectBrowser.java
@@ -25,7 +25,7 @@
 import android.media.MtpConstants;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.Mtp;
+import android.provider.Ptp;
 import android.util.Log;
 import android.view.View;
 import android.widget.AdapterView;
@@ -49,7 +49,7 @@
     private DeviceDisconnectedReceiver mDisconnectedReceiver;
 
     private static final String[] OBJECT_COLUMNS =
-        new String[] { Mtp.Object._ID, Mtp.Object.NAME, Mtp.Object.FORMAT, Mtp.Object.THUMB };
+        new String[] { Ptp.Object._ID, Ptp.Object.NAME, Ptp.Object.FORMAT, Ptp.Object.THUMB };
 
     static final int ID_COLUMN = 0;
     static final int NAME_COLUMN = 1;
@@ -74,9 +74,9 @@
             Cursor c;
             Uri uri;
             if (mObjectID == 0) {
-                uri = Mtp.Object.getContentUriForStorageChildren(mDeviceID, mStorageID);
+                uri = Ptp.Object.getContentUriForStorageChildren(mDeviceID, mStorageID);
             } else {
-                uri = Mtp.Object.getContentUriForObjectChildren(mDeviceID, mObjectID);
+                uri = Ptp.Object.getContentUriForObjectChildren(mDeviceID, mObjectID);
             }
             Log.d(TAG, "query " + uri);
             c = getContentResolver().query(uri, OBJECT_COLUMNS, null, null, null);
@@ -99,7 +99,7 @@
     protected void onListItemClick(ListView l, View v, int position, long id) {
         long rowID = mAdapter.getItemId(position);
         Cursor c = getContentResolver().query(
-                        Mtp.Object.getContentUri(mDeviceID, rowID),
+                        Ptp.Object.getContentUri(mDeviceID, rowID),
                         OBJECT_COLUMNS, null, null, null);
         Log.d(TAG, "query returned " + c + " count: " + c.getCount());
         long format = 0;
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
index 4e63128..3a6c6a4 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
@@ -24,7 +24,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Environment;
-import android.provider.Mtp;
+import android.provider.Ptp;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
@@ -51,21 +51,21 @@
     private DeviceDisconnectedReceiver mDisconnectedReceiver;
 
     private static final String[] OBJECT_COLUMNS =
-        new String[] {  Mtp.Object._ID,
-                        Mtp.Object.NAME,
-                        Mtp.Object.SIZE,
-                        Mtp.Object.THUMB_WIDTH,
-                        Mtp.Object.THUMB_HEIGHT,
-                        Mtp.Object.THUMB_SIZE,
-                        Mtp.Object.IMAGE_WIDTH,
-                        Mtp.Object.IMAGE_HEIGHT,
-                        Mtp.Object.IMAGE_DEPTH,
-                        Mtp.Object.SEQUENCE_NUMBER,
-                        Mtp.Object.DATE_CREATED,
-                        Mtp.Object.DATE_MODIFIED,
-                        Mtp.Object.KEYWORDS,
-                        Mtp.Object.THUMB,
-                        Mtp.Object.FORMAT,
+        new String[] {  Ptp.Object._ID,
+                        Ptp.Object.NAME,
+                        Ptp.Object.SIZE,
+                        Ptp.Object.THUMB_WIDTH,
+                        Ptp.Object.THUMB_HEIGHT,
+                        Ptp.Object.THUMB_SIZE,
+                        Ptp.Object.IMAGE_WIDTH,
+                        Ptp.Object.IMAGE_HEIGHT,
+                        Ptp.Object.IMAGE_DEPTH,
+                        Ptp.Object.SEQUENCE_NUMBER,
+                        Ptp.Object.DATE_CREATED,
+                        Ptp.Object.DATE_MODIFIED,
+                        Ptp.Object.KEYWORDS,
+                        Ptp.Object.THUMB,
+                        Ptp.Object.FORMAT,
                         };
 
     @Override
@@ -91,7 +91,7 @@
 
         if (mDeviceID != 0 && mObjectID != 0) {
         Cursor c = getContentResolver().query(
-                        Mtp.Object.getContentUri(mDeviceID, mObjectID),
+                        Ptp.Object.getContentUri(mDeviceID, mObjectID),
                         OBJECT_COLUMNS, null, null, null);
             c.moveToFirst();
             TextView view = (TextView)findViewById(R.id.name);
@@ -147,7 +147,7 @@
         dest.mkdirs();
         dest = new File(dest, mFileName);
 
-        Uri requestUri = Mtp.Object.getContentUriForImport(mDeviceID, mObjectID,
+        Uri requestUri = Ptp.Object.getContentUriForImport(mDeviceID, mObjectID,
                 dest.getAbsolutePath());
         Uri resultUri = getContentResolver().insert(requestUri, new ContentValues());
         Log.d(TAG, "save returned " + resultUri);
@@ -162,7 +162,7 @@
     }
 
     private void deleteObject() {
-        Uri uri = Mtp.Object.getContentUri(mDeviceID, mObjectID);
+        Uri uri = Ptp.Object.getContentUri(mDeviceID, mObjectID);
 
         Log.d(TAG, "deleting " + uri);
 
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java
index 4da88d6..62187b0 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/StorageBrowser.java
@@ -21,7 +21,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
-import android.provider.Mtp;
+import android.provider.Ptp;
 import android.util.Log;
 import android.view.View;
 import android.widget.ListAdapter;
@@ -40,7 +40,7 @@
     private DeviceDisconnectedReceiver mDisconnectedReceiver;
 
     private static final String[] STORAGE_COLUMNS =
-        new String[] { Mtp.Storage._ID, Mtp.Storage.DESCRIPTION };
+        new String[] { Ptp.Storage._ID, Ptp.Storage.DESCRIPTION };
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -54,7 +54,7 @@
         super.onResume();
 
         if (mDeviceID != 0) {
-            Cursor c = getContentResolver().query(Mtp.Storage.getContentUri(mDeviceID),
+            Cursor c = getContentResolver().query(Ptp.Storage.getContentUri(mDeviceID),
                     STORAGE_COLUMNS, null, null, null);
             Log.d(TAG, "query returned " + c);
             startManagingCursor(c);
@@ -62,7 +62,7 @@
             // Map Cursor columns to views defined in simple_list_item_1.xml
             mAdapter = new SimpleCursorAdapter(this,
                     android.R.layout.simple_list_item_1, c,
-                            new String[] { Mtp.Storage.DESCRIPTION },
+                            new String[] { Ptp.Storage.DESCRIPTION },
                             new int[] { android.R.id.text1, android.R.id.text2 });
             setListAdapter(mAdapter);
         }
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png
deleted file mode 100644
index 9ababb7..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_battery_mini.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png
deleted file mode 100644
index ffbd2d3..0000000
--- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_wifi_mini.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
index e81cd7c..9216030 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png
index e81cd7c..9216030 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
index c56ae8a..e529f6f 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png
index bf720cbb..e529f6f 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png
new file mode 100644
index 0000000..02c27ee
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_1x.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
index 38e3b24..57558ad 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png
index 3559497..57558ad 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
index 6f98e72..e4425b2 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png
index 8b932a6..e4425b2 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png
new file mode 100644
index 0000000..84ac927
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_3g.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
index 759631c..09de6b0 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png
index 45c4509..09de6b0 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png
new file mode 100644
index 0000000..13cae40
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_edge.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
index da5b02c..690b5f6c7 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_flightmode.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png
new file mode 100644
index 0000000..d0a4fd0
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_gprs.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png
new file mode 100644
index 0000000..05976bd
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_hsdpa.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png
new file mode 100644
index 0000000..2cc3cd6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_signal_roam.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png
index 2989939..1c59b2a 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0_fully.png
new file mode 100644
index 0000000..1c59b2a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_0_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png
index 6f40cb4..32e9165 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png
index 60e3794a..32e9165 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_1_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png
index ab1b0c67..ea71298 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png
index eb25919..ea71298 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_2_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png
index b574115..869a497 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png
index 14cd8fa..869a497 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_3_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png
index 3b81ceb..1711c82 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png
index ef72980..1711c82 100644
--- a/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png
+++ b/packages/SystemUI/res/drawable-mdpi/stat_sys_wifi_signal_4_fully.png
Binary files differ
diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml
index 4fa306e..488dbba 100644
--- a/packages/SystemUI/res/layout-xlarge/status_bar.xml
+++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml
@@ -80,21 +80,24 @@
                     android:layout_width="48dip"
                     android:layout_height="match_parent"
                     android:orientation="horizontal"
+                    android:gravity="center"
                     >
                     <ImageView
-                        android:id="@+id/battery"
-                        android:layout_height="wrap_content"
-                        android:layout_width="wrap_content"
-                        android:layout_gravity="top"
-                        android:layout_marginTop="18dp"
-                        />
-                    <ImageView
                         android:id="@+id/network"
                         android:layout_height="wrap_content"
                         android:layout_width="wrap_content"
                         android:layout_gravity="top"
-                        android:layout_marginTop="14dp"
-                        android:src="@drawable/ic_sysbar_wifi_mini"
+                        android:layout_marginTop="19dp"
+                        android:layout_marginRight="4dp"
+                        />
+                    <ImageView
+                        android:id="@+id/battery"
+                        android:layout_height="wrap_content"
+                        android:layout_width="wrap_content"
+                        android:layout_gravity="top"
+                        android:layout_marginTop="19dp"
+                        android:layout_marginLeft="2dp"
+                        android:layout_marginRight="2dp"
                         />
                 </LinearLayout>
             </LinearLayout>
diff --git a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
index f9e2d5e..5fa8b3b 100644
--- a/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
+++ b/packages/SystemUI/res/layout-xlarge/sysbar_panel_notifications.xml
@@ -78,7 +78,6 @@
             android:layout_below="@id/date"
             android:layout_marginTop="16dp"
             android:layout_marginLeft="48dp"
-            android:src="@drawable/ic_sysbar_battery_mini"
             android:baseline="17dp"
             />
 
@@ -98,7 +97,6 @@
             android:layout_width="wrap_content"
             android:layout_toRightOf="@id/battery_text"
             android:layout_alignBaseline="@id/battery"
-            android:src="@drawable/ic_sysbar_wifi_mini"
             android:baseline="21dp"
             />
 
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 3ad199e..18e8273 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -71,6 +71,7 @@
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:fadingEdge="none"
+            android:overScrollMode="ifContentScrolls"
             >
             <LinearLayout
                 android:id="@+id/notificationLinearLayout"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
index 4ff2429..18003dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkController.java
@@ -37,6 +37,7 @@
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.util.Slog;
+import android.view.View;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -57,8 +58,6 @@
     boolean mHspaDataDistinguishable;
     final TelephonyManager mPhone;
     boolean mDataConnected;
-    int mPhoneSignalIconId;
-    int mDataIconId;
     IccCard.State mSimState = IccCard.State.READY;
     int mPhoneState = TelephonyManager.CALL_STATE_IDLE;
     int mDataState = TelephonyManager.DATA_DISCONNECTED;
@@ -66,6 +65,10 @@
     ServiceState mServiceState;
     SignalStrength mSignalStrength;
     int[] mDataIconList = TelephonyIcons.DATA_G[0];
+    int mPhoneSignalIconId;
+    int mDataDirectionIconId;
+    int mDataSignalIconId;
+    int mDataTypeIconId;
  
     // wifi
     final WifiManager mWifiManager;
@@ -81,11 +84,17 @@
 
     // our ui
     Context mContext;
-    ArrayList<ImageView> mPhoneIconViews = new ArrayList<ImageView>();
-    ArrayList<ImageView> mDataIconViews = new ArrayList<ImageView>();
+    ArrayList<ImageView> mPhoneSignalIconViews = new ArrayList<ImageView>();
+    ArrayList<ImageView> mDataDirectionIconViews = new ArrayList<ImageView>();
+    ArrayList<ImageView> mWifiIconViews = new ArrayList<ImageView>();
+    ArrayList<ImageView> mCombinedSignalIconViews = new ArrayList<ImageView>();
+    ArrayList<ImageView> mDataTypeIconViews = new ArrayList<ImageView>();
     ArrayList<TextView> mLabelViews = new ArrayList<TextView>();
     int mLastPhoneSignalIconId = -1;
-    int mLastCombinedDataIconId = -1;
+    int mLastDataDirectionIconId = -1;
+    int mLastWifiIconId = -1;
+    int mLastCombinedSignalIconId = -1;
+    int mLastDataTypeIconId = -1;
     String mLastLabel = "";
     
     // yuck -- stop doing this here and put it in the framework
@@ -116,7 +125,6 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
         filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
-        filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
         context.registerReceiver(this, filter);
@@ -125,12 +133,24 @@
         mBatteryStats = BatteryStatsService.getService();
     }
 
-    public void addPhoneIconView(ImageView v) {
-        mPhoneIconViews.add(v);
+    public void addPhoneSignalIconView(ImageView v) {
+        mPhoneSignalIconViews.add(v);
     }
 
-    public void addCombinedDataIconView(ImageView v) {
-        mDataIconViews.add(v);
+    public void addDataDirectionIconView(ImageView v) {
+        mDataDirectionIconViews.add(v);
+    }
+
+    public void addWifiIconView(ImageView v) {
+        mWifiIconViews.add(v);
+    }
+
+    public void addCombinedSignalIconView(ImageView v) {
+        mCombinedSignalIconViews.add(v);
+    }
+
+    public void addDataTypeIconView(ImageView v) {
+        mDataTypeIconViews.add(v);
     }
 
     public void addLabelView(TextView v) {
@@ -141,7 +161,6 @@
         final String action = intent.getAction();
         if (action.equals(WifiManager.RSSI_CHANGED_ACTION)
                 || action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)
-                || action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)
                 || action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
             updateWifiState(intent);
             refreshViews();
@@ -317,12 +336,15 @@
             if (Settings.System.getInt(mContext.getContentResolver(),
                     Settings.System.AIRPLANE_MODE_ON, 0) == 1) {
                 mPhoneSignalIconId = R.drawable.stat_sys_signal_flightmode;
+                mDataSignalIconId = R.drawable.stat_sys_signal_flightmode;
             } else {
                 mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
+                mDataSignalIconId = R.drawable.stat_sys_signal_0; // note we use 0 instead of null
             }
         } else {
             if (mSignalStrength == null) {
                 mPhoneSignalIconId = R.drawable.stat_sys_signal_null;
+                mDataSignalIconId = R.drawable.stat_sys_signal_0; // note we use 0 instead of null
             } else if (isCdma()) {
                 // If 3G(EV) and 1x network are available than 3G should be
                 // displayed, displayed RSSI should be from the EV side.
@@ -340,6 +362,7 @@
                     iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[mInetCondition];
                 }
                 mPhoneSignalIconId = iconList[iconLevel];
+                mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel];
             } else {
                 int asu = mSignalStrength.getGsmSignalStrength();
 
@@ -362,6 +385,7 @@
                     iconList = TelephonyIcons.TELEPHONY_SIGNAL_STRENGTH[mInetCondition];
                 }
                 mPhoneSignalIconId = iconList[iconLevel];
+                mDataSignalIconId = TelephonyIcons.DATA_SIGNAL_STRENGTH[mInetCondition][iconLevel];
             }
         }
     }
@@ -370,36 +394,47 @@
         switch (net) {
             case TelephonyManager.NETWORK_TYPE_EDGE:
                 mDataIconList = TelephonyIcons.DATA_E[mInetCondition];
+                mDataTypeIconId = R.drawable.stat_sys_signal_edge;
                 break;
             case TelephonyManager.NETWORK_TYPE_UMTS:
                 mDataIconList = TelephonyIcons.DATA_3G[mInetCondition];
+                mDataTypeIconId = R.drawable.stat_sys_signal_3g;
                 break;
             case TelephonyManager.NETWORK_TYPE_HSDPA:
             case TelephonyManager.NETWORK_TYPE_HSUPA:
             case TelephonyManager.NETWORK_TYPE_HSPA:
                 if (mHspaDataDistinguishable) {
                     mDataIconList = TelephonyIcons.DATA_H[mInetCondition];
+                    mDataTypeIconId = R.drawable.stat_sys_signal_hsdpa;
                 } else {
                     mDataIconList = TelephonyIcons.DATA_3G[mInetCondition];
+                    mDataTypeIconId = R.drawable.stat_sys_signal_3g;
                 }
                 break;
             case TelephonyManager.NETWORK_TYPE_CDMA:
                 // display 1xRTT for IS95A/B
                 mDataIconList = TelephonyIcons.DATA_1X[mInetCondition];
+                mDataTypeIconId = R.drawable.stat_sys_signal_1x;
                 break;
             case TelephonyManager.NETWORK_TYPE_1xRTT:
                 mDataIconList = TelephonyIcons.DATA_1X[mInetCondition];
+                mDataTypeIconId = R.drawable.stat_sys_signal_1x;
                 break;
             case TelephonyManager.NETWORK_TYPE_EVDO_0: //fall through
             case TelephonyManager.NETWORK_TYPE_EVDO_A:
             case TelephonyManager.NETWORK_TYPE_EVDO_B:
                 mDataIconList = TelephonyIcons.DATA_3G[mInetCondition];
+                mDataTypeIconId = R.drawable.stat_sys_signal_3g;
                 break;
             // TODO - add support for NETWORK_TYPE_LTE and NETWORK_TYPE_EHRPD
             default:
                 mDataIconList = TelephonyIcons.DATA_G[mInetCondition];
+                mDataTypeIconId = R.drawable.stat_sys_signal_gprs;
             break;
         }
+        if ((isCdma() && isCdmaEri()) || mPhone.isNetworkRoaming()) {
+            mDataTypeIconId = R.drawable.stat_sys_signal_roam;
+        }
     }
 
     boolean isCdmaEri() {
@@ -436,7 +471,7 @@
                             iconId = mDataIconList[0];
                             break;
                     }
-                    mDataIconId = iconId;
+                    mDataDirectionIconId = iconId;
                 } else {
                     iconId = 0;
                     visible = false;
@@ -477,7 +512,7 @@
             Binder.restoreCallingIdentity(ident);
         }
 
-        mDataIconId = iconId;
+        mDataDirectionIconId = iconId;
         mDataConnected = visible;
     }
 
@@ -489,8 +524,7 @@
             mWifiEnabled = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
                     WifiManager.WIFI_STATE_UNKNOWN) == WifiManager.WIFI_STATE_ENABLED;
 
-        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)
-                || action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
+        } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
             final NetworkInfo networkInfo = (NetworkInfo)
                     intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
             boolean wasConnected = mWifiConnected;
@@ -575,8 +609,10 @@
     void refreshViews() {
         Context context = mContext;
 
-        int combinedDataIconId;
+        int combinedSignalIconId;
+        int dataTypeIconId;
         String label;
+        int N;
 
         if (mWifiConnected) {
             if (mWifiSsid == null) {
@@ -585,35 +621,84 @@
                 label = context.getString(R.string.system_panel_signal_meter_wifi_ssid_format,
                                       mWifiSsid);
             }
-            combinedDataIconId = mWifiIconId;
-        } else if (mDataConnected) {
-            label = context.getString(R.string.system_panel_signal_meter_data_connected);
-            combinedDataIconId = mDataIconId;
+            combinedSignalIconId = mWifiIconId;
+            dataTypeIconId = 0;
         } else {
-            label = context.getString(R.string.system_panel_signal_meter_disconnected);
-            combinedDataIconId = 0;
+            if (mDataConnected) {
+                label = context.getString(R.string.system_panel_signal_meter_data_connected);
+            } else {
+                label = context.getString(R.string.system_panel_signal_meter_disconnected);
+            }
+            combinedSignalIconId = mDataSignalIconId;
+            dataTypeIconId = mDataTypeIconId;
         }
 
-        int N;
+        if (false) {
+            Slog.d(TAG, "refreshViews combinedSignalIconId=0x"
+                    + Integer.toHexString(mPhoneSignalIconId)
+                    + " mPhoneSignalIconId=0x" + Integer.toHexString(mPhoneSignalIconId)
+                    + " mDataDirectionIconId=0x" + Integer.toHexString(mDataDirectionIconId)
+                    + " mDataSignalIconId=0x" + Integer.toHexString(mDataSignalIconId)
+                    + " mDataTypeIconId=0x" + Integer.toHexString(mDataTypeIconId)
+                    + " mWifiIconId=0x" + Integer.toHexString(mWifiIconId));
+        }
 
+        // the phone icon on phones
         if (mLastPhoneSignalIconId != mPhoneSignalIconId) {
             mLastPhoneSignalIconId = mPhoneSignalIconId;
-            N = mPhoneIconViews.size();
+            N = mPhoneSignalIconViews.size();
             for (int i=0; i<N; i++) {
-                ImageView v = mPhoneIconViews.get(i);
+                final ImageView v = mPhoneSignalIconViews.get(i);
                 v.setImageResource(mPhoneSignalIconId);
             }
         }
 
-        if (mLastCombinedDataIconId != combinedDataIconId) {
-            mLastCombinedDataIconId = combinedDataIconId;
-            N = mDataIconViews.size();
+        // the data icon on phones
+        if (mLastDataDirectionIconId != mDataDirectionIconId) {
+            mLastDataDirectionIconId = mDataDirectionIconId;
+            N = mDataDirectionIconViews.size();
             for (int i=0; i<N; i++) {
-                ImageView v = mDataIconViews.get(i);
-                v.setImageResource(combinedDataIconId);
+                final ImageView v = mDataDirectionIconViews.get(i);
+                v.setImageResource(mDataDirectionIconId);
             }
         }
 
+        // the wifi icon on phones
+        if (mLastWifiIconId != mWifiIconId) {
+            mLastWifiIconId = mWifiIconId;
+            N = mWifiIconViews.size();
+            for (int i=0; i<N; i++) {
+                final ImageView v = mWifiIconViews.get(i);
+                v.setImageResource(mWifiIconId);
+            }
+        }
+
+        // the combined data signal icon
+        if (mLastCombinedSignalIconId != combinedSignalIconId) {
+            mLastCombinedSignalIconId = combinedSignalIconId;
+            N = mCombinedSignalIconViews.size();
+            for (int i=0; i<N; i++) {
+                final ImageView v = mCombinedSignalIconViews.get(i);
+                v.setImageResource(combinedSignalIconId);
+            }
+        }
+
+        // the data network type overlay
+        if (mLastDataTypeIconId != dataTypeIconId) {
+            mLastDataTypeIconId = dataTypeIconId;
+            N = mDataTypeIconViews.size();
+            for (int i=0; i<N; i++) {
+                final ImageView v = mDataTypeIconViews.get(i);
+                if (dataTypeIconId == 0) {
+                    v.setVisibility(View.INVISIBLE);
+                } else {
+                    v.setVisibility(View.VISIBLE);
+                    v.setImageResource(dataTypeIconId);
+                }
+            }
+        }
+
+        // the label in the notification panel
         if (!mLastLabel.equals(label)) {
             mLastLabel = label;
             N = mLabelViews.size();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
index 050a746..94c68ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/TelephonyIcons.java
@@ -48,6 +48,8 @@
           R.drawable.stat_sys_r_signal_4_fully }
     };
 
+    static final int[][] DATA_SIGNAL_STRENGTH = TELEPHONY_SIGNAL_STRENGTH;
+
     //***** Data connection icons
 
     //GSM/UMTS
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index ab509ef..a934cd7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -157,7 +157,7 @@
         mBatteryController.addIconView((ImageView)mNotificationPanel.findViewById(R.id.battery));
         mBatteryController.addLabelView(
                 (TextView)mNotificationPanel.findViewById(R.id.battery_text));
-        mNetworkController.addCombinedDataIconView(
+        mNetworkController.addCombinedSignalIconView(
                 (ImageView)mNotificationPanel.findViewById(R.id.network));
         mNetworkController.addLabelView(
                 (TextView)mNotificationPanel.findViewById(R.id.network_text));
@@ -282,7 +282,7 @@
         mBatteryController = new BatteryController(mContext);
         mBatteryController.addIconView((ImageView)sb.findViewById(R.id.battery));
         mNetworkController = new NetworkController(mContext);
-        mNetworkController.addCombinedDataIconView((ImageView)sb.findViewById(R.id.network));
+        mNetworkController.addCombinedSignalIconView((ImageView)sb.findViewById(R.id.network));
 
         // The navigation buttons
         mNavigationArea = sb.findViewById(R.id.navigationArea);
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 5287289..1c1a46e 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -139,6 +139,10 @@
     static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
     static final int LONG_PRESS_POWER_SHUT_OFF = 2;
     
+    static final int LONG_PRESS_HOME_NOTHING = 0;
+    static final int LONG_PRESS_HOME_RECENT_DIALOG = 1;
+    static final int LONG_PRESS_HOME_RECENT_ACTIVITY = 2;
+
     // wallpaper is at the bottom, though the window manager may move it.
     static final int WALLPAPER_LAYER = 2;
     static final int APPLICATION_LAYER = 2;
@@ -327,8 +331,8 @@
     // Nothing to see here, move along...
     int mFancyRotationAnimation;
     
-    // Enable 3D recents based on config settings.
-    private Boolean mUse3dRecents;
+    // What we do when the user long presses on home
+    private int mLongPressOnHomeBehavior = -1;
 
     ShortcutManager mShortcutManager;
     PowerManager.WakeLock mBroadcastWakeLock;
@@ -565,8 +569,6 @@
              * the user lets go of the home key
              */
             mHomePressed = false;
-            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
-            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
             showRecentAppsDialog();
         }
     };
@@ -576,16 +578,32 @@
      */
     void showRecentAppsDialog() {
         // We can't initialize this in init() since the configuration hasn't been loaded yet.
-        if (mUse3dRecents == null) {
-            mUse3dRecents = mContext.getResources().getBoolean(R.bool.config_enableRecentApps3D);
+        if (mLongPressOnHomeBehavior < 0) {
+            mLongPressOnHomeBehavior
+                    = mContext.getResources().getInteger(R.integer.config_longPressOnPowerBehavior);
+            if (mLongPressOnHomeBehavior < LONG_PRESS_HOME_NOTHING ||
+                    mLongPressOnHomeBehavior > LONG_PRESS_HOME_RECENT_ACTIVITY) {
+                mLongPressOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
+            }
+        }
+
+        if (mLongPressOnHomeBehavior != LONG_PRESS_HOME_NOTHING) {
+            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+            sendCloseSystemWindows(SYSTEM_DIALOG_REASON_RECENT_APPS);
         }
         
         // Use 3d Recents dialog
-        if (mUse3dRecents) {
+        if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_DIALOG) {
+            // Fallback to dialog if we fail to launch the above.
+            if (mRecentAppsDialog == null) {
+                mRecentAppsDialog = new RecentApplicationsDialog(mContext);
+            }
+            mRecentAppsDialog.show();
+        } else if (mLongPressOnHomeBehavior == LONG_PRESS_HOME_RECENT_ACTIVITY) {
             try {
                 Intent intent = new Intent();
                 intent.setClassName("com.android.systemui", 
-                        "com.android.systemui.statusbar.RecentApplicationsActivity");
+                        "com.android.systemui.recent.RecentApplicationsActivity");
                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 
                         | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                 mContext.startActivity(intent);
@@ -594,12 +612,6 @@
                 Log.e(TAG, "Failed to launch RecentAppsIntent", e);
             }
         }
-
-        // Fallback to dialog if we fail to launch the above.
-        if (mRecentAppsDialog == null) {
-            mRecentAppsDialog = new RecentApplicationsDialog(mContext);
-        }
-        mRecentAppsDialog.show();
     }
     
     /** {@inheritDoc} */
diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java
index 0e45145..0a28da7 100644
--- a/services/java/com/android/server/DropBoxManagerService.java
+++ b/services/java/com/android/server/DropBoxManagerService.java
@@ -343,16 +343,17 @@
 
             if ((entry.flags & DropBoxManager.IS_TEXT) != 0 && (doPrint || !doFile)) {
                 DropBoxManager.Entry dbe = null;
+                InputStreamReader isr = null;
                 try {
                     dbe = new DropBoxManager.Entry(
                              entry.tag, entry.timestampMillis, entry.file, entry.flags);
 
                     if (doPrint) {
-                        InputStreamReader r = new InputStreamReader(dbe.getInputStream());
+                        isr = new InputStreamReader(dbe.getInputStream());
                         char[] buf = new char[4096];
                         boolean newline = false;
                         for (;;) {
-                            int n = r.read(buf);
+                            int n = isr.read(buf);
                             if (n <= 0) break;
                             out.append(buf, 0, n);
                             newline = (buf[n - 1] == '\n');
@@ -376,6 +377,12 @@
                     Slog.e(TAG, "Can't read: " + entry.file, e);
                 } finally {
                     if (dbe != null) dbe.close();
+                    if (isr != null) {
+                        try {
+                            isr.close();
+                        } catch (IOException unused) {
+                        }
+                    }
                 }
             }
 
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index b2cc6bd..723432d 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -240,10 +240,9 @@
     private InputMethodSubtype mCurrentSubtype;
 
     // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
-    private List<Pair<InputMethodInfo, InputMethodSubtype>> mShortcutInputMethodsAndSubtypes;
-    // This list is used for returning the pairs of InputMethodInfo and InputMethodSubtype through
-    // aidl. This list has imi1, subtype1 imi2, subtype2...
-    private List mShortcutInputMethodsAndSubtypesObjectList;
+    private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
+            mShortcutInputMethodsAndSubtypes =
+                new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
 
     /**
      * Set to true if our ServiceConnection is currently actively bound to
@@ -992,7 +991,7 @@
                 mCurMethodId = null;
                 unbindCurrentMethodLocked(true);
             }
-            mShortcutInputMethodsAndSubtypes = null;
+            mShortcutInputMethodsAndSubtypes.clear();
         } else {
             // There is no longer an input method set, so stop any current one.
             mCurMethodId = null;
@@ -1301,7 +1300,18 @@
     }
 
     public void setInputMethod(IBinder token, String id) {
-        setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID);
+        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
+    }
+
+    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+        synchronized (mMethodMap) {
+            if (subtype != null) {
+                setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
+                        mMethodMap.get(id), subtype.hashCode()));
+            } else {
+                setInputMethod(token, id);
+            }
+        }
     }
 
     public boolean switchToLastInputMethod(IBinder token) {
@@ -1310,7 +1320,7 @@
             if (lastIme != null) {
                 InputMethodInfo imi = mMethodMap.get(lastIme.first);
                 if (imi != null) {
-                    setInputMethodWithSubtype(token, lastIme.first, getSubtypeIdFromHashCode(
+                    setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode(
                             imi, Integer.valueOf(lastIme.second)));
                     return true;
                 }
@@ -1319,7 +1329,7 @@
         }
     }
 
-    private void setInputMethodWithSubtype(IBinder token, String id, int subtypeId) {
+    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
         synchronized (mMethodMap) {
             if (token == null) {
                 if (mContext.checkCallingOrSelfPermission(
@@ -1910,11 +1920,13 @@
     }
 
     private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
-        ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
-        for (int i = 0; i < subtypes.size(); ++i) {
-            InputMethodSubtype ims = subtypes.get(i);
-            if (subtypeHashCode == ims.hashCode()) {
-                return i;
+        if (imi != null) {
+            ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
+            for (int i = 0; i < subtypes.size(); ++i) {
+                InputMethodSubtype ims = subtypes.get(i);
+                if (subtypeHashCode == ims.hashCode()) {
+                    return i;
+                }
             }
         }
         return NOT_A_SUBTYPE_ID;
@@ -2022,7 +2034,6 @@
                     + mostApplicableIMI.getId() + "," + mostApplicableSubtypeId);
         }
         if (mostApplicableIMI != null && mostApplicableSubtypeId != NOT_A_SUBTYPE_ID) {
-            ArrayList<Parcelable> ret = new ArrayList<Parcelable>(2);
             return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
                     mostApplicableIMI.getSubtypes().get(mostApplicableSubtypeId));
         } else {
@@ -2067,25 +2078,37 @@
         }
     }
 
+    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
+            InputMethodSubtype subtype) {
+        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
+            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
+        } else {
+            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
+            subtypes.add(subtype);
+            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
+        }
+    }
+
     // TODO: We should change the return type from List to List<Parcelable>
     public List getShortcutInputMethodsAndSubtypes() {
         synchronized (mMethodMap) {
-            if (mShortcutInputMethodsAndSubtypesObjectList != null) {
+            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
                 // If there are no selected shortcut subtypes, the framework will try to find
                 // the most applicable subtype from all subtypes whose mode is
                 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
-                mShortcutInputMethodsAndSubtypes =
-                        new ArrayList<Pair<InputMethodInfo, InputMethodSubtype>>();
-                mShortcutInputMethodsAndSubtypes.add(
-                        findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
-                                SUBTYPE_MODE_VOICE));
-                mShortcutInputMethodsAndSubtypesObjectList = new ArrayList<Parcelable>();
-                for (Pair ime: mShortcutInputMethodsAndSubtypes) {
-                    mShortcutInputMethodsAndSubtypesObjectList.add(ime.first);
-                    mShortcutInputMethodsAndSubtypesObjectList.add(ime.second);
+                Pair<InputMethodInfo, InputMethodSubtype> info =
+                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
+                            SUBTYPE_MODE_VOICE);
+                addShortcutInputMethodAndSubtypes(info.first, info.second);
+            }
+            ArrayList ret = new ArrayList<Object>();
+            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
+                ret.add(imi);
+                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
+                    ret.add(subtype);
                 }
             }
-            return mShortcutInputMethodsAndSubtypesObjectList;
+            return ret;
         }
     }
 
diff --git a/services/java/com/android/server/UsbObserver.java b/services/java/com/android/server/UsbObserver.java
index cfa83be..4a7df8f 100644
--- a/services/java/com/android/server/UsbObserver.java
+++ b/services/java/com/android/server/UsbObserver.java
@@ -24,7 +24,7 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.UEventObserver;
-import android.provider.Mtp;
+import android.provider.Ptp;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.Slog;
@@ -155,7 +155,7 @@
     // called from JNI in monitorUsbHostBus()
     private void usbCameraAdded(int deviceID) {
         Intent intent = new Intent(Usb.ACTION_USB_CAMERA_ATTACHED,
-                                Mtp.Device.getContentUri(deviceID));
+                                Ptp.Device.getContentUri(deviceID));
         Log.d(TAG, "usbCameraAdded, sending " + intent);
         mContext.sendBroadcast(intent);
     }
@@ -163,7 +163,7 @@
     // called from JNI in monitorUsbHostBus()
     private void usbCameraRemoved(int deviceID) {
         Intent intent = new Intent(Usb.ACTION_USB_CAMERA_DETACHED,
-                                Mtp.Device.getContentUri(deviceID));
+                                Ptp.Device.getContentUri(deviceID));
         Log.d(TAG, "usbCameraRemoved, sending " + intent);
         mContext.sendBroadcast(intent);
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 1773eca..1a10cff 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -557,7 +557,7 @@
             = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>();
 
     /**
-     * Fingerprints (String.hashCode()) of stack traces that we've
+     * Fingerprints (hashCode()) of stack traces that we've
      * already logged DropBox entries for.  Guarded by itself.  If
      * something (rogue user app) forces this over
      * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared.
@@ -6671,7 +6671,7 @@
         ProcessRecord r = findAppProcess(app);
 
         if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
-            Integer stackFingerprint = info.crashInfo.stackTrace.hashCode();
+            Integer stackFingerprint = info.hashCode();
             boolean logIt = true;
             synchronized (mAlreadyLoggedViolatedStacks) {
                 if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) {
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index 3cb614f..faae89b 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -33,8 +33,8 @@
 
 /* fwd decls, so I can write this downward */
 ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets);
-ssize_t processAssets(Bundle* bundle, ZipFile* zip,
-                        const sp<AaptDir>& dir, const AaptGroupEntry& ge);
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
+                        const AaptGroupEntry& ge, const ResourceFilter* filter);
 bool processFile(Bundle* bundle, ZipFile* zip,
                         const sp<AaptGroup>& group, const sp<AaptFile>& file);
 bool okayToCompress(Bundle* bundle, const String8& pathName);
@@ -204,34 +204,45 @@
     const size_t N = assets->getGroupEntries().size();
     for (size_t i=0; i<N; i++) {
         const AaptGroupEntry& ge = assets->getGroupEntries()[i];
-        if (!filter.match(ge.toParams())) {
-            continue;
-        }
-        ssize_t res = processAssets(bundle, zip, assets, ge);
+
+        ssize_t res = processAssets(bundle, zip, assets, ge, &filter);
         if (res < 0) {
             return res;
         }
+
         count += res;
     }
 
     return count;
 }
 
-ssize_t processAssets(Bundle* bundle, ZipFile* zip,
-                      const sp<AaptDir>& dir, const AaptGroupEntry& ge)
+ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir,
+        const AaptGroupEntry& ge, const ResourceFilter* filter)
 {
     ssize_t count = 0;
 
     const size_t ND = dir->getDirs().size();
     size_t i;
     for (i=0; i<ND; i++) {
-        ssize_t res = processAssets(bundle, zip, dir->getDirs().valueAt(i), ge);
+        const sp<AaptDir>& subDir = dir->getDirs().valueAt(i);
+
+        const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0;
+
+        if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) {
+            continue;
+        }
+
+        ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL);
         if (res < 0) {
             return res;
         }
         count += res;
     }
 
+    if (filter != NULL && !filter->match(ge.toParams())) {
+        return count;
+    }
+
     const size_t NF = dir->getFiles().size();
     for (i=0; i<NF; i++) {
         sp<AaptGroup> gp = dir->getFiles().valueAt(i);
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 822262e..7b74506 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -150,7 +150,7 @@
 {
     return type == "anim" || type == "drawable" || type == "layout"
         || type == "values" || type == "xml" || type == "raw"
-        || type == "color" || type == "menu";
+        || type == "color" || type == "menu" || type == "mipmap";
 }
 
 static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true)
@@ -284,9 +284,9 @@
 }
 
 static status_t preProcessImages(Bundle* bundle, const sp<AaptAssets>& assets,
-                          const sp<ResourceTypeSet>& set)
+                          const sp<ResourceTypeSet>& set, const char* type)
 {
-    ResourceDirIterator it(set, String8("drawable"));
+    ResourceDirIterator it(set, String8(type));
     Vector<sp<AaptFile> > newNameFiles;
     Vector<String8> newNamePaths;
     bool hasErrors = false;
@@ -802,6 +802,7 @@
     sp<ResourceTypeSet> raws;
     sp<ResourceTypeSet> colors;
     sp<ResourceTypeSet> menus;
+    sp<ResourceTypeSet> mipmaps;
 
     ASSIGN_IT(drawable);
     ASSIGN_IT(layout);
@@ -810,6 +811,7 @@
     ASSIGN_IT(raw);
     ASSIGN_IT(color);
     ASSIGN_IT(menu);
+    ASSIGN_IT(mipmap);
 
     assets->setResources(resources);
     // now go through any resource overlays and collect their files
@@ -828,7 +830,8 @@
             !applyFileOverlay(bundle, assets, &xmls, "xml") ||
             !applyFileOverlay(bundle, assets, &raws, "raw") ||
             !applyFileOverlay(bundle, assets, &colors, "color") ||
-            !applyFileOverlay(bundle, assets, &menus, "menu")) {
+            !applyFileOverlay(bundle, assets, &menus, "menu") ||
+            !applyFileOverlay(bundle, assets, &mipmaps, "mipmap")) {
         return UNKNOWN_ERROR;
     }
 
@@ -836,7 +839,7 @@
 
     if (drawables != NULL) {
         if (bundle->getOutputAPKFile() != NULL) {
-            err = preProcessImages(bundle, assets, drawables);
+            err = preProcessImages(bundle, assets, drawables, "drawable");
         }
         if (err == NO_ERROR) {
             err = makeFileResources(bundle, assets, &table, drawables, "drawable");
@@ -848,6 +851,20 @@
         }
     }
 
+    if (mipmaps != NULL) {
+        if (bundle->getOutputAPKFile() != NULL) {
+            err = preProcessImages(bundle, assets, mipmaps, "mipmap");
+        }
+        if (err == NO_ERROR) {
+            err = makeFileResources(bundle, assets, &table, mipmaps, "mipmap");
+            if (err != NO_ERROR) {
+                hasErrors = true;
+            }
+        } else {
+            hasErrors = true;
+        }
+    }
+
     if (layouts != NULL) {
         err = makeFileResources(bundle, assets, &table, layouts, "layout");
         if (err != NO_ERROR) {
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index a77042a..196b06c 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -2539,7 +2539,7 @@
 }
 
 bool
-ResourceFilter::match(int axis, uint32_t value)
+ResourceFilter::match(int axis, uint32_t value) const
 {
     if (value == 0) {
         // they didn't specify anything so take everything
@@ -2555,7 +2555,7 @@
 }
 
 bool
-ResourceFilter::match(const ResTable_config& config)
+ResourceFilter::match(const ResTable_config& config) const
 {
     if (config.locale) {
         uint32_t locale = (config.country[1] << 24) | (config.country[0] << 16)
@@ -2608,6 +2608,8 @@
     const size_t N = mOrderedPackages.size();
     size_t pi;
 
+    const static String16 mipmap16("mipmap");
+
     bool useUTF8 = !bundle->getWantUTF16() && bundle->isMinSdkAtLeast(SDK_FROYO);
 
     // Iterate through all data, collecting all values (strings,
@@ -2630,7 +2632,10 @@
                 typeStrings.add(String16("<empty>"), false);
                 continue;
             }
-            typeStrings.add(t->getName(), false);
+            const String16 typeName(t->getName());
+            typeStrings.add(typeName, false);
+
+            const bool filterable = (typeName != mipmap16);
 
             const size_t N = t->getOrderedConfigs().size();
             for (size_t ci=0; ci<N; ci++) {
@@ -2641,7 +2646,7 @@
                 const size_t N = c->getEntries().size();
                 for (size_t ei=0; ei<N; ei++) {
                     ConfigDescription config = c->getEntries().keyAt(ei);
-                    if (!filter.match(config)) {
+                    if (filterable && !filter.match(config)) {
                         continue;
                     }
                     sp<Entry> e = c->getEntries().valueAt(ei);
@@ -2721,6 +2726,8 @@
                                 "Type name %s not found",
                                 String8(typeName).string());
 
+            const bool filterable = (typeName != mipmap16);
+
             const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0;
             
             // First write the typeSpec chunk, containing information about
@@ -2745,7 +2752,7 @@
                     (((uint8_t*)data->editData())
                         + typeSpecStart + sizeof(ResTable_typeSpec));
                 memset(typeSpecFlags, 0, sizeof(uint32_t)*N);
-                        
+
                 for (size_t ei=0; ei<N; ei++) {
                     sp<ConfigList> cl = t->getOrderedConfigs().itemAt(ei);
                     if (cl->getPublic()) {
@@ -2753,11 +2760,11 @@
                     }
                     const size_t CN = cl->getEntries().size();
                     for (size_t ci=0; ci<CN; ci++) {
-                        if (!filter.match(cl->getEntries().keyAt(ci))) {
+                        if (filterable && !filter.match(cl->getEntries().keyAt(ci))) {
                             continue;
                         }
                         for (size_t cj=ci+1; cj<CN; cj++) {
-                            if (!filter.match(cl->getEntries().keyAt(cj))) {
+                            if (filterable && !filter.match(cl->getEntries().keyAt(cj))) {
                                 continue;
                             }
                             typeSpecFlags[ei] |= htodl(
@@ -2794,7 +2801,7 @@
                       config.screenWidth,
                       config.screenHeight));
                       
-                if (!filter.match(config)) {
+                if (filterable && !filter.match(config)) {
                     continue;
                 }
                 
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 186c7ca..bbb8140 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -549,9 +549,9 @@
 public:
     ResourceFilter() : mData(), mContainsPseudo(false) {}
     status_t parse(const char* arg);
-    bool match(int axis, uint32_t value);
-    bool match(const ResTable_config& config);
-    inline bool containsPseudo() { return mContainsPseudo; }
+    bool match(int axis, uint32_t value) const;
+    bool match(const ResTable_config& config) const;
+    inline bool containsPseudo() const { return mContainsPseudo; }
 
 private:
     KeyedVector<int,SortedVector<uint32_t> > mData;
diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
index 6fd59c4..212223c 100644
--- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
+++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java
@@ -456,7 +456,11 @@
             // into is.read(...) This number is not related to the value passed
             // to mark(...) above.
             try {
-                bm = Bitmap_Delegate.createBitmap(is, Density.MEDIUM);
+                Density density = Density.MEDIUM;
+                if (opts != null) {
+                    density = Density.getEnum(opts.inDensity);
+                }
+                bm = Bitmap_Delegate.createBitmap(is, true, density);
             } catch (IOException e) {
                 return null;
             }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
index 0920497..b4c51b2 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java
@@ -75,32 +75,56 @@
 
     /**
      * Creates and returns a {@link Bitmap} initialized with the given file content.
+     *
+     * @param input the file from which to read the bitmap content
+     * @param isMutable whether the bitmap is mutable
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
      */
-    public static Bitmap createBitmap(File input, Density density) throws IOException {
+    public static Bitmap createBitmap(File input, boolean isMutable, Density density)
+            throws IOException {
         // create a delegate with the content of the file.
         Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input));
 
-        return createBitmap(delegate, density.getValue());
+        return createBitmap(delegate, isMutable, density.getValue());
     }
 
     /**
      * Creates and returns a {@link Bitmap} initialized with the given stream content.
+     *
+     * @param input the stream from which to read the bitmap content
+     * @param isMutable whether the bitmap is mutable
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
      */
-    public static Bitmap createBitmap(InputStream input, Density density) throws IOException {
+    public static Bitmap createBitmap(InputStream input, boolean isMutable, Density density)
+            throws IOException {
         // create a delegate with the content of the stream.
         Bitmap_Delegate delegate = new Bitmap_Delegate(ImageIO.read(input));
 
-        return createBitmap(delegate, density.getValue());
+        return createBitmap(delegate, isMutable, density.getValue());
     }
 
     /**
      * Creates and returns a {@link Bitmap} initialized with the given {@link BufferedImage}
+     *
+     * @param image the bitmap content
+     * @param isMutable whether the bitmap is mutable
+     * @param density the density associated with the bitmap
+     *
+     * @see Bitmap#isMutable()
+     * @see Bitmap#getDensity()
      */
-    public static Bitmap createBitmap(BufferedImage image, Density density) throws IOException {
+    public static Bitmap createBitmap(BufferedImage image, boolean isMutable, Density density)
+            throws IOException {
         // create a delegate with the given image.
         Bitmap_Delegate delegate = new Bitmap_Delegate(image);
 
-        return createBitmap(delegate, density.getValue());
+        return createBitmap(delegate, isMutable, density.getValue());
     }
 
     /**
@@ -153,7 +177,7 @@
         // create a delegate with the content of the stream.
         Bitmap_Delegate delegate = new Bitmap_Delegate(image);
 
-        return createBitmap(delegate, Bitmap.getDefaultDensity());
+        return createBitmap(delegate, mutable, Bitmap.getDefaultDensity());
     }
 
     /*package*/ static Bitmap nativeCopy(int srcBitmap, int nativeConfig, boolean isMutable) {
@@ -166,8 +190,7 @@
     }
 
     /*package*/ static void nativeRecycle(int nativeBitmap) {
-        // FIXME implement native delegate
-        throw new UnsupportedOperationException("Native delegate needed for Bitmap");
+        sManager.removeDelegate(nativeBitmap);
     }
 
     /*package*/ static boolean nativeCompress(int nativeBitmap, int format, int quality,
@@ -336,11 +359,11 @@
         mImage = image;
     }
 
-    private static Bitmap createBitmap(Bitmap_Delegate delegate, int density) {
+    private static Bitmap createBitmap(Bitmap_Delegate delegate, boolean isMutable, int density) {
         // get its native_int
         int nativeInt = sManager.addDelegate(delegate);
 
         // and create/return a new Bitmap with it
-        return new Bitmap(nativeInt, true /*isMutable*/, null /*ninePatchChunk*/, density);
+        return new Bitmap(nativeInt, isMutable, null /*ninePatchChunk*/, density);
     }
 }
diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
index cea07af..08f3c7a 100644
--- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java
@@ -960,7 +960,7 @@
      * Creates a new {@link Graphics2D} based on the {@link Paint} parameters.
      * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used.
      */
-    private Graphics2D getCustomGraphics(Paint_Delegate paint) {
+    /*package*/ Graphics2D getCustomGraphics(Paint_Delegate paint) {
         // make new one
         Graphics2D g = getGraphics2d();
         g = (Graphics2D)g.create();
diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
new file mode 100644
index 0000000..3d26e47
--- /dev/null
+++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.ninepatch.NinePatchChunk;
+
+import android.graphics.drawable.NinePatchDrawable;
+
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Delegate implementing the native methods of android.graphics.NinePatch
+ *
+ * Through the layoutlib_create tool, the original native methods of NinePatch have been replaced
+ * by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ */
+public class NinePatch_Delegate {
+
+    /**
+     * Cache map for {@link NinePatchChunk}.
+     * When the chunks are created they are serialized into a byte[], and both are put
+     * in the cache, using a {@link SoftReference} for the chunk. The default Java classes
+     * for {@link NinePatch} and {@link NinePatchDrawable} only reference to the byte[] data, and
+     * provide this for drawing.
+     * Using the cache map allows us to not have to deserialize the byte[] back into a
+     * {@link NinePatchChunk} every time a rendering is done.
+     */
+    private final static Map<byte[], SoftReference<NinePatchChunk>> sChunkCache =
+        new HashMap<byte[], SoftReference<NinePatchChunk>>();
+
+    // ---- Public Helper methods ----
+
+    /**
+     * Serializes the given chunk.
+     *
+     * @return the serialized data for the chunk.
+     */
+    public static byte[] serialize(NinePatchChunk chunk) {
+        // serialize the chunk to get a byte[]
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ObjectOutputStream oos = null;
+        try {
+            oos = new ObjectOutputStream(baos);
+            oos.writeObject(chunk);
+        } catch (IOException e) {
+            //FIXME log this.
+            return null;
+        } finally {
+            if (oos != null) {
+                try {
+                    oos.close();
+                } catch (IOException e) {
+                }
+            }
+        }
+
+        // get the array and add it to the cache
+        byte[] array = baos.toByteArray();
+        sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
+        return array;
+    }
+
+    // ---- native methods ----
+
+    /*package*/ static boolean isNinePatchChunk(byte[] chunk) {
+        NinePatchChunk chunkObject = getChunk(chunk);
+        if (chunkObject != null) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /*package*/ static void validateNinePatchChunk(int bitmap, byte[] chunk) {
+        // the default JNI implementation only checks that the byte[] has the same
+        // size as the C struct it represent. Since we cannot do the same check (serialization
+        // will return different size depending on content), we do nothing.
+    }
+
+    /*package*/ static void nativeDraw(int canvas_instance, RectF loc, int bitmap_instance,
+            byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
+        draw(canvas_instance,
+                (int) loc.left, (int) loc.top, (int) loc.width(), (int) loc.height(),
+                bitmap_instance, c, paint_instance_or_null,
+                destDensity, srcDensity);
+    }
+
+    /*package*/ static void nativeDraw(int canvas_instance, Rect loc, int bitmap_instance,
+            byte[] c, int paint_instance_or_null, int destDensity, int srcDensity) {
+        draw(canvas_instance,
+                loc.left, loc.top, loc.width(), loc.height(),
+                bitmap_instance, c, paint_instance_or_null,
+                destDensity, srcDensity);
+    }
+
+   private static void draw(int canvas_instance,
+           int left, int top, int right, int bottom,
+           int bitmap_instance, byte[] c, int paint_instance_or_null,
+           int destDensity, int srcDensity) {
+       // get the delegate from the native int.
+       Bitmap_Delegate bitmap_delegate = Bitmap_Delegate.getDelegate(bitmap_instance);
+       if (bitmap_delegate == null) {
+           assert false;
+           return;
+       }
+
+       if (c == null) {
+           // not a 9-patch?
+           BufferedImage image = bitmap_delegate.getImage();
+           Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance,
+                   new Rect(0, 0, image.getWidth(), image.getHeight()),
+                   new Rect(left, top, right, bottom),
+                   paint_instance_or_null, destDensity, srcDensity);
+           return;
+       }
+
+       NinePatchChunk chunkObject = getChunk(c);
+       assert chunkObject != null;
+       if (chunkObject == null) {
+           return;
+       }
+
+       Canvas_Delegate canvas_delegate = Canvas_Delegate.getDelegate(canvas_instance);
+       if (canvas_delegate == null) {
+           assert false;
+           return;
+       }
+
+       // this one can be null
+       Paint_Delegate paint_delegate = Paint_Delegate.getDelegate(paint_instance_or_null);
+
+       Graphics2D graphics;
+       if (paint_delegate != null) {
+           graphics = canvas_delegate.getCustomGraphics(paint_delegate);
+       } else {
+           graphics = canvas_delegate.getGraphics2d();
+       }
+
+       try {
+           chunkObject.draw(bitmap_delegate.getImage(), graphics,
+                   left, top, right - left, bottom - top);
+       } finally {
+           if (paint_delegate != null) {
+               graphics.dispose();
+           }
+       }
+
+    }
+
+    /*package*/ static int nativeGetTransparentRegion(int bitmap, byte[] chunk, Rect location) {
+        return 0;
+    }
+
+    // ---- Private Helper methods ----
+
+    /**
+     * Returns a {@link NinePatchChunk} object for the given serialized representation.
+     *
+     * If the chunk is present in the cache then the object from the cache is returned, otherwise
+     * the array is deserialized into a {@link NinePatchChunk} object.
+     *
+     * @param array the serialized representation of the chunk.
+     * @return the NinePatchChunk or null if deserialization failed.
+     */
+    private static NinePatchChunk getChunk(byte[] array) {
+        SoftReference<NinePatchChunk> chunkRef = sChunkCache.get(array);
+        NinePatchChunk chunk = chunkRef.get();
+        if (chunk == null) {
+            ByteArrayInputStream bais = new ByteArrayInputStream(array);
+            ObjectInputStream ois = null;
+            try {
+                ois = new ObjectInputStream(bais);
+                chunk = (NinePatchChunk) ois.readObject();
+
+                // put back the chunk in the cache
+                if (chunk != null) {
+                    sChunkCache.put(array, new SoftReference<NinePatchChunk>(chunk));
+                }
+            } catch (IOException e) {
+                // FIXME: log this
+                return null;
+            } catch (ClassNotFoundException e) {
+                // FIXME: log this
+                return null;
+            } finally {
+                if (ois != null) {
+                    try {
+                        ois.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+        }
+
+        return chunk;
+    }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index e691fdf..35ba73d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -26,7 +26,7 @@
 import com.android.layoutlib.bridge.android.BridgeAssetManager;
 import com.android.layoutlib.bridge.impl.FontLoader;
 import com.android.layoutlib.bridge.impl.LayoutSceneImpl;
-import com.android.ninepatch.NinePatch;
+import com.android.ninepatch.NinePatchChunk;
 import com.android.tools.layoutlib.create.MethodAdapter;
 import com.android.tools.layoutlib.create.OverrideMethod;
 
@@ -73,13 +73,13 @@
 
     private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
         new HashMap<Object, Map<String, SoftReference<Bitmap>>>();
-    private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache =
-        new HashMap<Object, Map<String, SoftReference<NinePatch>>>();
+    private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
+        new HashMap<Object, Map<String, SoftReference<NinePatchChunk>>>();
 
     private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache =
         new HashMap<String, SoftReference<Bitmap>>();
-    private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache =
-        new HashMap<String, SoftReference<NinePatch>>();
+    private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
+        new HashMap<String, SoftReference<NinePatchChunk>>();
 
     private static Map<String, Map<String, Integer>> sEnumValueMap;
 
@@ -252,23 +252,23 @@
     }
 
     /**
-     * Sets a 9 patch in a project cache or in the framework cache.
+     * Sets a 9 patch chunk in a project cache or in the framework cache.
      * @param value the path of the 9 patch
      * @param ninePatch the 9 patch object
      * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
      */
-    public static void setCached9Patch(String value, NinePatch ninePatch, Object projectKey) {
+    public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
         if (projectKey != null) {
-            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
+            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
 
             if (map == null) {
-                map = new HashMap<String, SoftReference<NinePatch>>();
+                map = new HashMap<String, SoftReference<NinePatchChunk>>();
                 sProject9PatchCache.put(projectKey, map);
             }
 
-            map.put(value, new SoftReference<NinePatch>(ninePatch));
+            map.put(value, new SoftReference<NinePatchChunk>(ninePatch));
         } else {
-            sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch));
+            sFramework9PatchCache.put(value, new SoftReference<NinePatchChunk>(ninePatch));
         }
     }
 
@@ -436,24 +436,24 @@
     }
 
     /**
-     * Returns the 9 patch for a specific path, from a specific project cache, or from the
+     * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
      * framework cache.
      * @param value the path of the 9 patch
      * @param projectKey the key of the project, or null to query the framework cache.
      * @return the cached 9 patch or null if not found.
      */
-    public static NinePatch getCached9Patch(String value, Object projectKey) {
+    public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
         if (projectKey != null) {
-            Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey);
+            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
 
             if (map != null) {
-                SoftReference<NinePatch> ref = map.get(value);
+                SoftReference<NinePatchChunk> ref = map.get(value);
                 if (ref != null) {
                     return ref.get();
                 }
             }
         } else {
-            SoftReference<NinePatch> ref = sFramework9PatchCache.get(value);
+            SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
             if (ref != null) {
                 return ref.get();
             }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/NinePatchDrawable.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/NinePatchDrawable.java
deleted file mode 100644
index 4efa631..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/NinePatchDrawable.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.layoutlib.bridge.android;
-
-import com.android.ninepatch.NinePatch;
-
-import android.graphics.Canvas;
-import android.graphics.Canvas_Delegate;
-import android.graphics.ColorFilter;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-
-public class NinePatchDrawable extends Drawable {
-
-    private NinePatch m9Patch;
-
-    public NinePatchDrawable(NinePatch ninePatch) {
-        m9Patch = ninePatch;
-    }
-
-    @Override
-    public int getMinimumWidth() {
-        return m9Patch.getWidth();
-    }
-
-    @Override
-    public int getMinimumHeight() {
-        return m9Patch.getHeight();
-    }
-
-    /**
-     * Return the intrinsic width of the underlying drawable object.  Returns
-     * -1 if it has no intrinsic width, such as with a solid color.
-     */
-    @Override
-    public int getIntrinsicWidth() {
-        return m9Patch.getWidth();
-    }
-
-    /**
-     * Return the intrinsic height of the underlying drawable object. Returns
-     * -1 if it has no intrinsic height, such as with a solid color.
-     */
-    @Override
-    public int getIntrinsicHeight() {
-        return m9Patch.getHeight();
-    }
-
-    /**
-     * Return in padding the insets suggested by this Drawable for placing
-     * content inside the drawable's bounds. Positive values move toward the
-     * center of the Drawable (set Rect.inset). Returns true if this drawable
-     * actually has a padding, else false. When false is returned, the padding
-     * is always set to 0.
-     */
-    @Override
-    public boolean getPadding(Rect padding) {
-        int[] padd = new int[4];
-        m9Patch.getPadding(padd);
-        padding.left = padd[0];
-        padding.top = padd[1];
-        padding.right = padd[2];
-        padding.bottom = padd[3];
-        return true;
-    }
-
-    @Override
-    public void draw(Canvas canvas) {
-        Rect r = getBounds();
-        Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
-        m9Patch.draw(canvasDelegate.getGraphics2d(), r.left, r.top, r.width(), r.height());
-
-        return;
-    }
-
-
-    // ----------- Not implemented methods ---------------
-
-
-    @Override
-    public int getOpacity() {
-        // FIXME
-        return 0xFF;
-    }
-
-    @Override
-    public void setAlpha(int arg0) {
-        // FIXME !
-    }
-
-    @Override
-    public void setColorFilter(ColorFilter arg0) {
-        // FIXME
-    }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
index 2e3f9a8..f7d249e 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java
@@ -313,10 +313,12 @@
 
             // create an Android bitmap around the BufferedImage
             Bitmap bitmap = Bitmap_Delegate.createBitmap(mImage,
+                    true /*isMutable*/,
                     Density.getEnum(mParams.getDensity()));
 
             // create a Canvas around the Android bitmap
             Canvas canvas = new Canvas(bitmap);
+            canvas.setDensity(mParams.getDensity());
 
             // to set the logger, get the native delegate
             Canvas_Delegate canvasDelegate = Canvas_Delegate.getDelegate(canvas);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 3e506b8..ceb8a0d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -22,8 +22,8 @@
 import com.android.layoutlib.bridge.Bridge;
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.android.NinePatchDrawable;
 import com.android.ninepatch.NinePatch;
+import com.android.ninepatch.NinePatchChunk;
 
 import org.kxml2.io.KXmlParser;
 import org.xmlpull.v1.XmlPullParser;
@@ -31,9 +31,12 @@
 
 import android.graphics.Bitmap;
 import android.graphics.Bitmap_Delegate;
+import android.graphics.NinePatch_Delegate;
+import android.graphics.Rect;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.NinePatchDrawable;
 import android.util.TypedValue;
 
 import java.io.File;
@@ -121,15 +124,38 @@
         if (lowerCaseValue.endsWith(NinePatch.EXTENSION_9PATCH)) {
             File file = new File(stringValue);
             if (file.isFile()) {
-                NinePatch ninePatch = Bridge.getCached9Patch(stringValue,
+                // see if we still have both the chunk and the bitmap in the caches
+                NinePatchChunk chunk = Bridge.getCached9Patch(stringValue,
+                        isFramework ? null : context.getProjectKey());
+                Bitmap bitmap = Bridge.getCachedBitmap(stringValue,
                         isFramework ? null : context.getProjectKey());
 
-                if (ninePatch == null) {
+                // if either chunk or bitmap is null, then we reload the 9-patch file.
+                if (chunk == null || bitmap == null) {
                     try {
-                        ninePatch = NinePatch.load(file.toURL(), false /* convert */);
+                        NinePatch ninePatch = NinePatch.load(file.toURL(), false /* convert */);
+                        if (ninePatch != null) {
+                            if (chunk == null) {
+                                chunk = ninePatch.getChunk();
 
-                        Bridge.setCached9Patch(stringValue, ninePatch,
-                                isFramework ? null : context.getProjectKey());
+                                Bridge.setCached9Patch(stringValue, chunk,
+                                        isFramework ? null : context.getProjectKey());
+                            }
+
+                            if (bitmap == null) {
+                                Density density = Density.MEDIUM;
+                                if (value instanceof IDensityBasedResourceValue) {
+                                    density = ((IDensityBasedResourceValue)value).getDensity();
+                                }
+
+                                bitmap = Bitmap_Delegate.createBitmap(ninePatch.getImage(),
+                                        false /*isMutable*/,
+                                        density);
+
+                                Bridge.setCachedBitmap(stringValue, bitmap,
+                                        isFramework ? null : context.getProjectKey());
+                            }
+                        }
                     } catch (MalformedURLException e) {
                         // URL is wrong, we'll return null below
                     } catch (IOException e) {
@@ -137,8 +163,13 @@
                     }
                 }
 
-                if (ninePatch != null) {
-                    return new NinePatchDrawable(ninePatch);
+                if (chunk != null && bitmap != null) {
+                    int[] padding = chunk.getPadding();
+                    Rect paddingRect = new Rect(padding[0], padding[1], padding[2], padding[3]);
+
+                    return new NinePatchDrawable(context.getResources(), bitmap,
+                            NinePatch_Delegate.serialize(chunk),
+                            paddingRect, null);
                 }
             }
 
@@ -174,29 +205,17 @@
                             isFramework ? null : context.getProjectKey());
 
                     if (bitmap == null) {
-                        // always create the cache copy in the original density.
-                        bitmap = Bitmap_Delegate.createBitmap(bmpFile, Density.MEDIUM);
+                        Density density = Density.MEDIUM;
+                        if (value instanceof IDensityBasedResourceValue) {
+                            density = ((IDensityBasedResourceValue)value).getDensity();
+                        }
+
+                        bitmap = Bitmap_Delegate.createBitmap(bmpFile, false /*isMutable*/,
+                                density);
                         Bridge.setCachedBitmap(stringValue, bitmap,
                                 isFramework ? null : context.getProjectKey());
                     }
 
-                    try {
-                        if (value instanceof IDensityBasedResourceValue) {
-                            Density density = ((IDensityBasedResourceValue)value).getDensity();
-                            if (density != Density.MEDIUM) {
-                                // create a copy of the bitmap
-                                bitmap = Bitmap.createBitmap(bitmap);
-
-                                // apply the density
-                                bitmap.setDensity(density.getValue());
-                            }
-                        }
-                    } catch (NoClassDefFoundError error) {
-                        // look like we're running in an older version of ADT that doesn't include
-                        // the new layoutlib_api. Let's just ignore this, the drawing will just be
-                        // wrong.
-                    }
-
                     return new BitmapDrawable(context.getResources(), bitmap);
                 } catch (IOException e) {
                     // we'll return null below
diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java
index 23351ab..a3219e7 100644
--- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java
+++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/NinePatchTest.java
@@ -48,5 +48,4 @@
         assertEquals(36, mPatch.getWidth());
         assertEquals(25, mPatch.getHeight());
     }
-
 }
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index b9c71133..bb2e6b3 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -109,6 +109,7 @@
         "android.graphics.DashPathEffect",
         "android.graphics.LinearGradient",
         "android.graphics.Matrix",
+        "android.graphics.NinePatch",
         "android.graphics.Paint",
         "android.graphics.PathEffect",
         "android.graphics.PorterDuffXfermode",
diff --git a/tools/validatekeymaps/Android.mk b/tools/validatekeymaps/Android.mk
new file mode 100644
index 0000000..90979e1
--- /dev/null
+++ b/tools/validatekeymaps/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright 2010 The Android Open Source Project
+#
+# Keymap validation tool.
+#
+
+# This tool is prebuilt if we're doing an app-only build.
+ifeq ($(TARGET_BUILD_APPS),)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	Main.cpp
+
+LOCAL_CFLAGS := -Wall -Werror
+
+#LOCAL_C_INCLUDES +=
+
+LOCAL_STATIC_LIBRARIES := \
+	libui \
+	libutils \
+	libcutils
+
+ifeq ($(HOST_OS),linux)
+LOCAL_LDLIBS += -lpthread
+endif
+
+LOCAL_MODULE := validatekeymaps
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_EXECUTABLE)
+
+endif # TARGET_BUILD_APPS
diff --git a/tools/validatekeymaps/Main.cpp b/tools/validatekeymaps/Main.cpp
new file mode 100644
index 0000000..6ec223b
--- /dev/null
+++ b/tools/validatekeymaps/Main.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <ui/KeyCharacterMap.h>
+#include <ui/KeyLayoutMap.h>
+#include <utils/String8.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace android;
+
+static const char* gProgName = "validatekeymaps";
+
+enum FileType {
+    FILETYPE_UNKNOWN,
+    FILETYPE_KEYLAYOUT,
+    FILETYPE_KEYCHARACTERMAP,
+};
+
+
+static void usage() {
+    fprintf(stderr, "Keymap Validation Tool\n\n");
+    fprintf(stderr, "Usage:\n");
+    fprintf(stderr,
+        " %s [FILENAME.kl] [FILENAME.kcm] [...]\n"
+        "   Validates the specified key layout and/or key character map files.\n\n", gProgName);
+}
+
+static FileType getFileType(const char* filename) {
+    const char *extension = strrchr(filename, '.');
+    if (extension) {
+        if (strcmp(extension, ".kl") == 0) {
+            return FILETYPE_KEYLAYOUT;
+        }
+        if (strcmp(extension, ".kcm") == 0) {
+            return FILETYPE_KEYCHARACTERMAP;
+        }
+    }
+    return FILETYPE_UNKNOWN;
+}
+
+static bool validateFile(const char* filename) {
+    fprintf(stdout, "Validating file '%s'...\n", filename);
+
+    FileType fileType = getFileType(filename);
+    switch (fileType) {
+    case FILETYPE_UNKNOWN:
+        fprintf(stderr, "File extension must be .kl or .kcm.\n\n");
+        return false;
+
+    case FILETYPE_KEYLAYOUT: {
+        KeyLayoutMap* map;
+        status_t status = KeyLayoutMap::load(String8(filename), &map);
+        if (status) {
+            fprintf(stderr, "Error %d parsing key layout file.\n\n", status);
+            return false;
+        }
+        break;
+    }
+
+    case FILETYPE_KEYCHARACTERMAP: {
+        KeyCharacterMap* map;
+        status_t status = KeyCharacterMap::load(String8(filename), &map);
+        if (status) {
+            fprintf(stderr, "Error %d parsing key character map file.\n\n", status);
+            return false;
+        }
+        break;
+    }
+    }
+
+    fputs("No errors.\n\n", stdout);
+    return true;
+}
+
+int main(int argc, const char** argv) {
+    if (argc < 2) {
+        usage();
+        return 1;
+    }
+
+    int result = 0;
+    for (int i = 1; i < argc; i++) {
+        if (!validateFile(argv[i])) {
+            result = 1;
+        }
+    }
+
+    if (result) {
+        fputs("Failed!\n", stderr);
+    } else {
+        fputs("Success.\n", stdout);
+    }
+    return result;
+}
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 90abd02..7e3df1a 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1529,7 +1529,7 @@
     }
 
     void setNetworkAvailable(boolean available) {
-        sendMessage(CMD_SET_NETWORK_AVAILABLE, available ? 1 : 0);
+        sendMessage(obtainMessage(CMD_SET_NETWORK_AVAILABLE, available ? 1 : 0, 0));
     }
 
     /********************************************************