Merge "Grant permissions to apps on sd when enabling/disabling packages."
diff --git a/api/current.xml b/api/current.xml
index 64f0c23..cd11b1d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -199698,6 +199698,8 @@
 >
 <implements name="android.widget.ExpandableListAdapter">
 </implements>
+<implements name="android.widget.HeterogeneousExpandableList">
+</implements>
 <constructor name="BaseExpandableListAdapter"
  type="android.widget.BaseExpandableListAdapter"
  static="false"
@@ -199717,6 +199719,32 @@
  visibility="public"
 >
 </method>
+<method name="getChildType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="groupPosition" type="int">
+</parameter>
+<parameter name="childPosition" type="int">
+</parameter>
+</method>
+<method name="getChildTypeCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getCombinedChildId"
  return="long"
  abstract="false"
@@ -199745,6 +199773,30 @@
 <parameter name="groupId" type="long">
 </parameter>
 </method>
+<method name="getGroupType"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="groupPosition" type="int">
+</parameter>
+</method>
+<method name="getGroupTypeCount"
+ return="int"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="isEmpty"
  return="boolean"
  abstract="false"
@@ -203487,6 +203539,64 @@
 </parameter>
 </method>
 </class>
+<interface name="HeterogeneousExpandableList"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="getChildType"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="groupPosition" type="int">
+</parameter>
+<parameter name="childPosition" type="int">
+</parameter>
+</method>
+<method name="getChildTypeCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getGroupType"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="groupPosition" type="int">
+</parameter>
+</method>
+<method name="getGroupTypeCount"
+ return="int"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+</interface>
 <class name="HorizontalScrollView"
  extends="android.widget.FrameLayout"
  abstract="false"
diff --git a/core/java/android/bluetooth/ScoSocket.java b/core/java/android/bluetooth/ScoSocket.java
index 116310a..b65a99a 100644
--- a/core/java/android/bluetooth/ScoSocket.java
+++ b/core/java/android/bluetooth/ScoSocket.java
@@ -86,14 +86,14 @@
     /** Connect this SCO socket to the given BT address.
      *  Does not block.
      */
-    public synchronized boolean connect(String address) {
+    public synchronized boolean connect(String address, String name) {
         if (DBG) log("connect() " + this);
         if (mState != STATE_READY) {
             if (DBG) log("connect(): Bad state");
             return false;
         }
         acquireWakeLock();
-        if (connectNative(address)) {
+        if (connectNative(address, name)) {
             mState = STATE_CONNECTING;
             return true;
         } else {
@@ -102,7 +102,7 @@
             return false;
         }
     }
-    private native boolean connectNative(String address);
+    private native boolean connectNative(String address, String name);
 
     /** Accept incoming SCO connections.
      *  Does not block.
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index 816f8a8..72ceb9b 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -139,7 +139,10 @@
             if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) {
                 Log.v(TAG, "** warning ** Finalized DbObj (id#" + nStatement + ")");
             }
-            Log.w(TAG, "finalizer should never be called on sql: " + mSqlStmt, mStackTrace);
+            int len = mSqlStmt.length();
+            Log.w(TAG, "Releasing statement in a finalizer. Please ensure " +
+                    "that you explicitly call close() on your cursor: " +
+                    mSqlStmt.substring(0, (len > 100) ? 100 : len), mStackTrace);
             releaseSqlStatement();
         } finally {
             super.finalize();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 984e48b..7e748c0 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5728,7 +5728,7 @@
      * of the thumb within the scrollbar's track.</p>
      *
      * <p>The range is expressed in arbitrary units that must be the same as the
-     * units used by {@link #computeHorizontalScrollRange()} and
+     * units used by {@link #computeVerticalScrollRange()} and
      * {@link #computeVerticalScrollOffset()}.</p>
      *
      * <p>The default extent is the drawing height of this view.</p>
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 176169e..2709cff 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -28,11 +28,11 @@
 public class DecelerateInterpolator implements Interpolator {
     public DecelerateInterpolator() {
     }
-    
+
     /**
      * Constructor
      * 
-     * @param factor Degree to which the animation should be eased. Seting factor to 1.0f produces
+     * @param factor Degree to which the animation should be eased. Setting factor to 1.0f produces
      *        an upside-down y=x^2 parabola. Increasing factor above 1.0f makes exaggerates the
      *        ease-out effect (i.e., it starts even faster and ends evens slower)
      */
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 622d22d..1becf9e 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1903,15 +1903,8 @@
     // Expects y in view coordinates
     private int pinLocY(int y) {
         if (mInOverScrollMode) return y;
-        int titleH = getTitleHeight();
-        // if the titlebar is still visible, just pin against 0
-        if (y <= titleH) {
-            return Math.max(y, 0);
-        }
-        // convert to 0-based coordinate (subtract the title height)
-        // pin(), and then add the title height back in
-        return pinLoc(y - titleH, getViewHeight(),
-                      computeVerticalScrollRange()) + titleH;
+        return pinLoc(y, getViewHeightWithTitle(),
+                      computeVerticalScrollRange() + getTitleHeight());
     }
 
     /**
@@ -3133,7 +3126,7 @@
                 mOverScrollBackground = new Paint();
                 Bitmap bm = BitmapFactory.decodeResource(
                         mContext.getResources(),
-                        com.android.internal.R.drawable.pattern_underwear);
+                        com.android.internal.R.drawable.status_bar_background);
                 mOverScrollBackground.setShader(new BitmapShader(bm,
                         Shader.TileMode.REPEAT, Shader.TileMode.REPEAT));
             }
@@ -4203,12 +4196,6 @@
     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();
-        }
     }
 
     @Override
@@ -5371,7 +5358,7 @@
 
     private int computeMaxScrollY() {
         return Math.max(computeVerticalScrollRange() + getTitleHeight()
-                - getViewHeightWithTitle(), getTitleHeight());
+                - getViewHeightWithTitle(), 0);
     }
 
     public void flingScroll(int vx, int vy) {
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index a78429e..86011d7 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -32,6 +32,7 @@
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.KeyEvent;
@@ -2388,6 +2389,7 @@
     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) {
@@ -2561,7 +2563,7 @@
         /**
          * Tracks the decay of a fling scroll
          */
-        private OverScroller mScroller;
+        private final OverScroller mScroller;
 
         /**
          * Y value reported by mScroller on the previous fling
@@ -2598,6 +2600,21 @@
 
         void startOverfling(int initialVelocity) {
             mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 0, 0, 0, getHeight());
+            edgeReached();
+            mTouchMode = TOUCH_MODE_OVERFLING;
+            invalidate();
+            post(this);
+        }
+
+        void edgeReached() {
+            mScroller.notifyVerticalEdgeReached(mScrollY, 0, Integer.MAX_VALUE);
+            mTouchMode = TOUCH_MODE_OVERFLING;
+            invalidate();
+            post(this);
+        }
+
+        void marginReached() {
+            mScroller.notifyVerticalBoundaryReached(mScrollY, 0);
             mTouchMode = TOUCH_MODE_OVERFLING;
             invalidate();
             post(this);
@@ -2677,11 +2694,7 @@
                         overscrollBy(0, overshoot, 0, mScrollY, 0, 0,
                                 0, getOverscrollMax(), false);
                     }
-                    float vel = scroller.getCurrVelocity();
-                    if (delta > 0) {
-                        vel = -vel;
-                    }
-                    startOverfling(Math.round(vel));
+                    edgeReached();
                     break;
                 }
 
@@ -2738,7 +2751,7 @@
         private int mBoundPos;
         private int mLastSeenPos;
         private int mScrollDuration;
-        private int mExtraScroll;
+        private final int mExtraScroll;
         
         PositionScroller() {
             mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength();
@@ -3977,7 +3990,7 @@
             for (int i = 0; i < count; i++) {
                 if (activeViews[i] != null) {
                     result = false;
-                    android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
+                    Log.d(ViewDebug.CONSISTENCY_LOG_TAG,
                             "AbsListView " + this + " has a view in its active recycler: " +
                                     activeViews[i]);
                 }
@@ -4005,12 +4018,12 @@
             final View view = scrap.get(i);
             if (view.getParent() != null) {
                 result = false;
-                android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
+                Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
                         " has a view in its scrap heap still attached to a parent: " + view);
             }
             if (indexOfChild(view) >= 0) {
                 result = false;
-                android.util.Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
+                Log.d(ViewDebug.CONSISTENCY_LOG_TAG, "AbsListView " + this +
                         " has a view in its scrap heap that is also a direct child: " + view);
             }
         }
diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java
index 5b52107..eb2da71 100644
--- a/core/java/android/widget/AutoCompleteTextView.java
+++ b/core/java/android/widget/AutoCompleteTextView.java
@@ -1629,7 +1629,10 @@
                 // of iterating throught he list of observers.
                 post(new Runnable() {
                     public void run() {
-                        updateDropDownForFilter(mAdapter.getCount());
+                        final ListAdapter adapter = mAdapter;
+                        if (adapter != null) {
+                            updateDropDownForFilter(adapter.getCount());
+                        }
                     }
                 });
             }
diff --git a/core/java/android/widget/BaseExpandableListAdapter.java b/core/java/android/widget/BaseExpandableListAdapter.java
index 1bba7f0..396b7ae 100644
--- a/core/java/android/widget/BaseExpandableListAdapter.java
+++ b/core/java/android/widget/BaseExpandableListAdapter.java
@@ -18,7 +18,6 @@
 
 import android.database.DataSetObservable;
 import android.database.DataSetObserver;
-import android.view.KeyEvent;
 
 /**
  * Base class for a {@link ExpandableListAdapter} used to provide data and Views
@@ -31,7 +30,8 @@
  * @see SimpleExpandableListAdapter
  * @see SimpleCursorTreeAdapter
  */
-public abstract class BaseExpandableListAdapter implements ExpandableListAdapter {
+public abstract class BaseExpandableListAdapter implements ExpandableListAdapter, 
+        HeterogeneousExpandableList {
     private final DataSetObservable mDataSetObservable = new DataSetObservable();
     
     public void registerDataSetObserver(DataSetObserver observer) {
@@ -102,5 +102,37 @@
     public boolean isEmpty() {
         return getGroupCount() == 0;
     }
-    
+
+
+    /**
+     * {@inheritDoc}
+     * @return 0 for any group or child position, since only one child type count is declared.
+     */
+    public int getChildType(int groupPosition, int childPosition) {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return 1 as a default value in BaseExpandableListAdapter.
+     */
+    public int getChildTypeCount() {
+        return 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return 0 for any groupPosition, since only one group type count is declared.
+     */
+    public int getGroupType(int groupPosition) {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return 1 as a default value in BaseExpandableListAdapter.
+     */
+    public int getGroupTypeCount() {
+        return 1;
+    }
 }
diff --git a/core/java/android/widget/ExpandableListAdapter.java b/core/java/android/widget/ExpandableListAdapter.java
index b75983c..7f6781b 100644
--- a/core/java/android/widget/ExpandableListAdapter.java
+++ b/core/java/android/widget/ExpandableListAdapter.java
@@ -17,7 +17,6 @@
 package android.widget;
 
 import android.database.DataSetObserver;
-import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -108,7 +107,7 @@
     /**
      * Gets a View that displays the given group. This View is only for the
      * group--the Views for the group's children will be fetched using
-     * getChildrenView.
+     * {@link #getChildView(int, int, boolean, View, ViewGroup)}.
      * 
      * @param groupPosition the position of the group for which the View is
      *            returned
diff --git a/core/java/android/widget/ExpandableListConnector.java b/core/java/android/widget/ExpandableListConnector.java
index 01d3a4a..2ff6b70 100644
--- a/core/java/android/widget/ExpandableListConnector.java
+++ b/core/java/android/widget/ExpandableListConnector.java
@@ -442,8 +442,8 @@
 
         View retValue;
         if (posMetadata.position.type == ExpandableListPosition.GROUP) {
-            retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos, posMetadata
-                    .isExpanded(), convertView, parent);
+            retValue = mExpandableListAdapter.getGroupView(posMetadata.position.groupPos,
+                    posMetadata.isExpanded(), convertView, parent);
         } else if (posMetadata.position.type == ExpandableListPosition.CHILD) {
             final boolean isLastChild = posMetadata.groupMetadata.lastChildFlPos == flatListPos;
             
@@ -464,10 +464,21 @@
         final ExpandableListPosition pos = getUnflattenedPos(flatListPos).position;
 
         int retValue;
-        if (pos.type == ExpandableListPosition.GROUP) {
-            retValue = 0;
+        if (mExpandableListAdapter instanceof HeterogeneousExpandableList) {
+            HeterogeneousExpandableList adapter =
+                    (HeterogeneousExpandableList) mExpandableListAdapter;
+            if (pos.type == ExpandableListPosition.GROUP) {
+                retValue = adapter.getGroupType(pos.groupPos);
+            } else {
+                final int childType = adapter.getChildType(pos.groupPos, pos.childPos);
+                retValue = adapter.getGroupTypeCount() + childType;
+            }
         } else {
-            retValue = 1;
+            if (pos.type == ExpandableListPosition.GROUP) {
+                retValue = 0;
+            } else {
+                retValue = 1;
+            }
         }
         
         pos.recycle();
@@ -477,7 +488,13 @@
 
     @Override
     public int getViewTypeCount() {
-        return 2;
+        if (mExpandableListAdapter instanceof HeterogeneousExpandableList) {
+            HeterogeneousExpandableList adapter =
+                    (HeterogeneousExpandableList) mExpandableListAdapter;
+            return adapter.getGroupTypeCount() + adapter.getChildTypeCount();
+        } else {
+            return 2;
+        }
     }
     
     @Override
diff --git a/core/java/android/widget/HeterogeneousExpandableList.java b/core/java/android/widget/HeterogeneousExpandableList.java
new file mode 100644
index 0000000..1292733
--- /dev/null
+++ b/core/java/android/widget/HeterogeneousExpandableList.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2006 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.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Additional methods that when implemented make an
+ * {@link ExpandableListAdapter} take advantage of the {@link Adapter} view type
+ * mechanism.
+ * 
+ * An {@link ExpandableListAdapter} declares one view type for its group items
+ * and one view type for its child items. Although adapted for most {@link ExpandableListView}s,
+ * these values should be tuned heterogeneous {@link ExpandableListView}s. Lists that contain
+ * different types of group and/or child item views, should use an adapter that implements this
+ * interface. This way, the recycled views that will be provided to
+ * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+ * and
+ * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+ * will be of the appropriate group or child type, resulting in a more efficient reuse of the
+ * previously created views.
+ */
+public interface HeterogeneousExpandableList {
+    /**
+     * Get the type of group View that will be created by
+     * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+     * . for the specified group item.
+     * 
+     * @param groupPosition the position of the group for which the type should be returned.
+     * @return An integer representing the type of group View. Two group views should share the same
+     *         type if one can be converted to the other in
+     *         {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+     *         . Note: Integers must be in the range 0 to {@link #getGroupTypeCount} - 1.
+     *         {@link android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE} can also be returned.
+     * @see android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE
+     * @see getGroupTypeCount()
+     */
+    int getGroupType(int groupPosition);
+
+    /**
+     * Get the type of child View that will be created by
+     * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+     * for the specified child item.
+     * 
+     * @param groupPosition the position of the group that the child resides in
+     * @param childPosition the position of the child with respect to other children in the group
+     * @return An integer representing the type of child View. Two child views should share the same
+     *         type if one can be converted to the other in
+     *         {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+     *         Note: Integers must be in the range 0 to {@link #getChildTypeCount} - 1.
+     *         {@link android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE} can also be returned.
+     * @see android.widget.Adapter#IGNORE_ITEM_VIEW_TYPE
+     * @see getChildTypeCount()
+     */
+    int getChildType(int groupPosition, int childPosition);
+
+    /**
+     * <p>
+     * Returns the number of types of group Views that will be created by
+     * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+     * . Each type represents a set of views that can be converted in
+     * {@link android.widget.ExpandableListAdapter#getGroupView(int, boolean, View, ViewGroup)}
+     * . If the adapter always returns the same type of View for all group items, this method should
+     * return 1.
+     * </p>
+     * <p>
+     * This method will only be called when the adapter is set on the {@link AdapterView}.
+     * </p>
+     * 
+     * @return The number of types of group Views that will be created by this adapter.
+     * @see getChildTypeCount()
+     * @see getGroupType()
+     */
+    int getGroupTypeCount();
+
+    /**
+     * <p>
+     * Returns the number of types of child Views that will be created by
+     * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+     * . Each type represents a set of views that can be converted in
+     * {@link android.widget.ExpandableListAdapter#getChildView(int, int, boolean, View, ViewGroup)}
+     * , for any group. If the adapter always returns the same type of View for
+     * all child items, this method should return 1.
+     * </p>
+     * <p>
+     * This method will only be called when the adapter is set on the {@link AdapterView}.
+     * </p>
+     * 
+     * @return The total number of types of child Views that will be created by this adapter.
+     * @see getGroupTypeCount()
+     * @see getChildType()
+     */
+    int getChildTypeCount();
+}
diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java
index 8469c8b..6258024 100644
--- a/core/java/android/widget/OverScroller.java
+++ b/core/java/android/widget/OverScroller.java
@@ -17,8 +17,8 @@
 package android.widget;
 
 import android.content.Context;
-import android.view.animation.AccelerateDecelerateInterpolator;
-import android.view.animation.DecelerateInterpolator;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
 
 /**
  * This class encapsulates scrolling with the ability to overshoot the bounds
@@ -27,110 +27,34 @@
  * 
  * @hide Pending API approval
  */
-public class OverScroller {
-    private static final int SPRINGBACK_DURATION = 150;
-    private static final int OVERFLING_DURATION = 150;
-    
-    private static final int MODE_DEFAULT = 0;
-    private static final int MODE_OVERFLING = 1;
-    private static final int MODE_SPRINGBACK = 2;
-    
-    private Scroller mDefaultScroller;
-    private Scroller mDecelScroller;
-    private Scroller mAccelDecelScroller;
-    private Scroller mCurrScroller;
-    
-    private int mScrollMode = MODE_DEFAULT;
-    
-    private int mMinimumX;
-    private int mMinimumY;
-    private int mMaximumX;
-    private int mMaximumY;
-    
-    public OverScroller(Context context) {
-        mDefaultScroller = new Scroller(context);
-        mDecelScroller = new Scroller(context, new DecelerateInterpolator());
-        mAccelDecelScroller = new Scroller(context, new AccelerateDecelerateInterpolator());
-        mCurrScroller = mDefaultScroller;
-    }
-    
+public class OverScroller extends Scroller {
+
+    // Identical to mScrollers, but casted to MagneticOverScroller. 
+    private MagneticOverScroller mOverScrollerX;
+    private MagneticOverScroller mOverScrollerY;
+
     /**
-     * Call this when you want to know the new location.  If it returns true,
-     * the animation is not yet finished.  loc will be altered to provide the
-     * new location.
-     */ 
-    public boolean computeScrollOffset() {
-        boolean inProgress = mCurrScroller.computeScrollOffset();
-        
-        switch (mScrollMode) {
-        case MODE_OVERFLING:
-            if (!inProgress) {
-                // Overfling ended
-                if (springback(mCurrScroller.getCurrX(), mCurrScroller.getCurrY(),
-                        mMinimumX, mMaximumX, mMinimumY, mMaximumY, mAccelDecelScroller)) {
-                    return mCurrScroller.computeScrollOffset();
-                } else {
-                    mCurrScroller = mDefaultScroller;
-                    mScrollMode = MODE_DEFAULT;
-                }
-            }
-            break;
-            
-        case MODE_SPRINGBACK:
-            if (!inProgress) {
-                mCurrScroller = mDefaultScroller;
-                mScrollMode = MODE_DEFAULT;
-            }
-            break;
-            
-        case MODE_DEFAULT:
-            // Fling/autoscroll - did we go off the edge?
-            if (inProgress) {
-                Scroller scroller = mCurrScroller;
-                final int x = scroller.getCurrX();
-                final int y = scroller.getCurrY();
-                final int minX = mMinimumX;
-                final int maxX = mMaximumX;
-                final int minY = mMinimumY;
-                final int maxY = mMaximumY;
-                if (x < minX || x > maxX || y < minY || y > maxY) {
-                    final int startx = scroller.getStartX();
-                    final int starty = scroller.getStartY();
-                    final int time = scroller.timePassed();
-                    final float timeSecs = time / 1000.f;
-                    final float xvel = ((x - startx) / timeSecs);
-                    final float yvel = ((y - starty) / timeSecs);
-                    
-                    if ((x < minX && xvel > 0) || (y < minY && yvel > 0) ||
-                            (x > maxX && xvel < 0) || (y > maxY && yvel < 0)) {
-                        // If our velocity would take us back into valid areas,
-                        // try to springback rather than overfling.
-                        if (springback(x, y, minX, maxX, minY, maxY)) {
-                            return mCurrScroller.computeScrollOffset();
-                        }
-                    } else {
-                        overfling(x, y, xvel, yvel);
-                        return mCurrScroller.computeScrollOffset();
-                    }
-                }
-            }
-            break;
-        }
-        
-        return inProgress;
+     * Creates an OverScroller with a viscous fluid scroll interpolator.
+     * @param context
+     */
+    public OverScroller(Context context) {
+        this(context, null);
     }
-    
-    private void overfling(int startx, int starty, float xvel, float yvel) {
-        Scroller scroller = mDecelScroller;
-        final float durationSecs = (OVERFLING_DURATION / 1000.f);
-        int dx = (int)(xvel * durationSecs) / 8;
-        int dy = (int)(yvel * durationSecs) / 8;
-        mCurrScroller.abortAnimation();
-        scroller.startScroll(startx, starty, dx, dy, OVERFLING_DURATION);
-        mCurrScroller = scroller;
-        mScrollMode = MODE_OVERFLING;
+
+    /**
+     * Creates a Scroller with the specified interpolator. If the interpolator is
+     * null, the default (viscous) interpolator will be used.
+     */
+    public OverScroller(Context context, Interpolator interpolator) {
+        super(context, interpolator);
     }
-    
+
+    @Override
+    void instantiateScrollers() {
+        mScrollerX = mOverScrollerX = new MagneticOverScroller();
+        mScrollerY = mOverScrollerY = new MagneticOverScroller();
+    }
+
     /**
      * Call this when you want to 'spring back' into a valid coordinate range.
      *
@@ -140,310 +64,262 @@
      * @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/startY was
+     * @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) {
-        return springback(startX, startY, minX, maxX, minY, maxY, mDecelScroller);
-    }
-    
-    private boolean springback(int startX, int startY, int minX, int maxX,
-            int minY, int maxY, Scroller scroller) {
-        int xoff = 0;
-        int yoff = 0;
-        if (startX < minX) {
-            xoff = minX - startX;
-        } else if (startX > maxX) {
-            xoff = maxX - startX;
-        }
-        if (startY < minY) {
-            yoff = minY - startY;
-        } else if (startY > maxY) {
-            yoff = maxY - startY;
-        }
-        
-        if (xoff != 0 || yoff != 0) {
-            mCurrScroller.abortAnimation();
-            scroller.startScroll(startX, startY, xoff, yoff, SPRINGBACK_DURATION);
-            mCurrScroller = scroller;
-            mScrollMode = MODE_SPRINGBACK;
-            return true;
-        }
-        
-        return false;
+    public boolean springback(int startX, int startY, int minX, int maxX, int minY, int maxY) {
+        mMode = FLING_MODE;
+        return mOverScrollerX.springback(startX, minX, maxX)
+                || mOverScrollerY.springback(startY, minY, maxY);
     }
 
-    /**
-     * 
-     * Returns whether the scroller has finished scrolling.
-     * 
-     * @return True if the scroller has finished scrolling, false otherwise.
-     */
-    public final boolean isFinished() {
-        return mCurrScroller.isFinished();
-    }
-
-    /**
-     * 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 mCurrScroller.getCurrX();
-    }
-    
-    /**
-     * 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 mCurrScroller.getCurrY();
-    }
-    
-    /**
-     * Stops the animation, resets any springback/overfling and completes
-     * any standard flings/scrolls in progress.
-     */
-    public void abortAnimation() {
-        mCurrScroller.abortAnimation();
-        mCurrScroller = mDefaultScroller;
-        mScrollMode = MODE_DEFAULT;
-        mCurrScroller.abortAnimation();
-    }
-    
-    /**
-     * Start scrolling by providing a starting point and the distance to travel.
-     * The scroll will use the default value of 250 milliseconds for the
-     * duration. This version does not spring back to boundaries.
-     * 
-     * @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) {
-        final int minX = Math.min(startX, startX + dx);
-        final int maxX = Math.max(startX, startX + dx);
-        final int minY = Math.min(startY, startY + dy);
-        final int maxY = Math.max(startY, startY + dy);
-        startScroll(startX, startY, dx, dy, minX, maxX, minY, maxY);
-    }
-    
-    /**
-     * Start scrolling by providing a starting point and the distance to travel.
-     * The scroll will use the default value of 250 milliseconds for the
-     * duration. This version will spring back to the provided boundaries if
-     * the scroll value would take it too far.
-     * 
-     * @param startX Starting horizontal scroll offset in pixels. Positive
-     *        numbers will scroll the content to the left.
-     * @param startY Starting vertical scroll offset in pixels. Positive numbers
-     *        will scroll the content up.
-     * @param dx Horizontal distance to travel. Positive numbers will scroll the
-     *        content to the left.
-     * @param dy Vertical distance to travel. Positive numbers will scroll the
-     *        content up.
-     * @param minX Minimum X value. The scroller will not scroll past this
-     *        point.
-     * @param maxX Maximum X value. The scroller will not scroll past this
-     *        point.
-     * @param minY Minimum Y value. The scroller will not scroll past this
-     *        point.
-     * @param maxY Maximum Y value. The scroller will not scroll past this
-     *        point.
-     */
-    public void startScroll(int startX, int startY, int dx, int dy,
-            int minX, int maxX, int minY, int maxY) {
-        mCurrScroller.abortAnimation();
-        mCurrScroller = mDefaultScroller;
-        mScrollMode = MODE_DEFAULT;
-        mMinimumX = minX;
-        mMaximumX = maxX; 
-        mMinimumY = minY;
-        mMaximumY = maxY;
-        mCurrScroller.startScroll(startX, startY, dx, dy);
-    }
-
-    /**
-     * 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) {
-        mCurrScroller.abortAnimation();
-        mCurrScroller = mDefaultScroller;
-        mScrollMode = MODE_DEFAULT;
-        mMinimumX = Math.min(startX, startX + dx);
-        mMinimumY = Math.min(startY, startY + dy);
-        mMaximumX = Math.max(startX, startX + dx);
-        mMaximumY = Math.max(startY, startY + dy);
-        mCurrScroller.startScroll(startX, startY, dx, dy, duration);
-    }
-    
-    /**
-     * Returns the duration of the active scroll in progress; standard, fling,
-     * springback, or overfling. Does not account for any overflings or springback
-     * that may result.
-     */
-    public int getDuration() {
-        return mCurrScroller.getDuration();
-    }
-
-    /**
-     * Start scrolling based on a fling gesture. The distance travelled 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.
-     * @param maxX Maximum X value. The scroller will not scroll past this
-     *        point.
-     * @param minY Minimum Y value. The scroller will not scroll past this
-     *        point.
-     * @param maxY Maximum Y value. The scroller will not scroll past this
-     *        point.
-     */
+    @Override
     public void fling(int startX, int startY, int velocityX, int velocityY,
             int minX, int maxX, int minY, int maxY) {
-        this.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
+        fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
     }
 
     /**
-     * Start scrolling based on a fling gesture. The distance travelled will
+     * 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.
+     *            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.
+     *            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.
+     *            direction will be possible.
      * @param overY Overfling range. If > 0, vertical overfling in either
-     *        direction will be possible.
+     *            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) {
-        mCurrScroller = mDefaultScroller;
-        mScrollMode = MODE_DEFAULT;
-        mMinimumX = minX;
-        mMaximumX = maxX;
-        mMinimumY = minY;
-        mMaximumY = maxY;
-        mCurrScroller.fling(startX, startY, velocityX, velocityY, 
-                minX - overX, maxX + overX, minY - overY, maxY + overY);
+        mMode = FLING_MODE;
+        mOverScrollerX.fling(startX, velocityX, minX, maxX, overX);
+        mOverScrollerY.fling(startY, velocityY, minY, maxY, overY);
+    }
+
+    void notifyHorizontalBoundaryReached(int startX, int finalX) {
+        mOverScrollerX.springback(startX, finalX, finalX);
+    }
+
+    void notifyVerticalBoundaryReached(int startY, int finalY) {
+        mOverScrollerY.springback(startY, finalY, finalY);
+    }
+
+    void notifyHorizontalEdgeReached(int startX, int finalX, int overX) {
+        mOverScrollerX.notifyEdgeReached(startX, finalX, overX);
+    }
+
+    void notifyVerticalEdgeReached(int startY, int finalY, int overY) {
+        mOverScrollerY.notifyEdgeReached(startY, finalY, overY);
     }
 
     /**
-     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     * Returns whether the current Scroller position is overscrolled or still within the minimum and
+     * maximum bounds provided in the
+     * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method.
      * 
-     * @return The final X offset as an absolute distance from the origin.
-     */
-    public int getFinalX() {
-        return mCurrScroller.getFinalX();
-    }
-    
-    /**
-     * Returns where the scroll will end. Valid only for "fling" scrolls.
+     * 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 The final Y offset as an absolute distance from the origin.
+     * @return true when the current position is overscrolled.
      */
-    public int getFinalY() {
-        return mCurrScroller.getFinalY();
+    public boolean isOverscrolled() {
+        return ((!mOverScrollerX.mFinished && mOverScrollerX.mState != MagneticOverScroller.TO_EDGE) ||
+                (!mOverScrollerY.mFinished && mOverScrollerY.mState != MagneticOverScroller.TO_EDGE));
     }
-    
-    /**
-     * @hide
-     * Returns the current velocity.
-     *
-     * @return The original velocity less the deceleration. Result may be
-     * negative.
-     */
-    public float getCurrVelocity() {
-        return mCurrScroller.getCurrVelocity();
-    }
-    
-    /**
-     * 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)
-     */
-    public void extendDuration(int extend) {
-        if (mScrollMode == MODE_DEFAULT) {
-            mDefaultScroller.extendDuration(extend);
+
+    static class MagneticOverScroller extends Scroller.MagneticScroller {
+        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;
+
+        // When the scroll goes beyond the edges limits, the deceleration is
+        // multiplied by this coefficient, so that the return to a valid
+        // position is faster.
+        private static final float OVERSCROLL_DECELERATION_COEF = 16.0f;
+
+        // 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 = 200.0f;
+
+        // Could be made public for tuning, but applications would no longer
+        // have the same look and feel.
+        private static final float BOUNCE_COEFFICIENT = 0.4f;
+
+        /*
+         * Get a signed deceleration that will reduce the velocity.
+         */
+        @Override
+        float getDeceleration(int velocity) {
+            float decelerationY = super.getDeceleration(velocity);
+            if (mState != TO_EDGE) {
+                decelerationY *= OVERSCROLL_DECELERATION_COEF;
+            }
+            return decelerationY;
         }
-    }
-    
-    /**
-     * 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)
-     */
-    public void setFinalX(int newX) {
-        if (mScrollMode == MODE_DEFAULT) {
-            if (newX < mMinimumX) {
-                mMinimumX = newX;
+
+        boolean springback(int start, int min, int max) {
+            mFinished = true;
+
+            mStart = start;
+            mVelocity = 0;
+
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+            mDuration = 0;
+
+            if (start < min) {
+                startSpringback(start, min, -1);
+            } else if (start > max) {
+                startSpringback(start, max, 1);
             }
-            if (newX > mMaximumX) {
-                mMaximumX = newX;
-            }
-            mDefaultScroller.setFinalX(newX);
+
+            return !mFinished;
         }
-    }
-    
-    /**
-     * 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)
-     */
-    public void setFinalY(int newY) {
-        if (mScrollMode == MODE_DEFAULT) {
-            if (newY < mMinimumY) {
-                mMinimumY = newY;
+
+        private void startSpringback(int start, int end, int sign) {
+            mFinished = false;
+            mState = TO_BOUNCE;
+            mDeceleration = getDeceleration(sign);
+            mFinal = end;
+            mDuration = (int) (1000.0f * Math.sqrt(2.0f * (end - start) / mDeceleration));
+        }
+
+        void fling(int start, int velocity, int min, int max, int over) {
+            mState = TO_EDGE;
+            mOver = over;
+
+            super.fling(start, velocity, min, max);
+
+            if (mStart > max) {
+                if (mStart >= max + over) {
+                    springback(max + over, min, max);
+                } else {
+                    // Make sure the deceleration brings us back to edge
+                    mVelocity = velocity > 0 ? velocity : -velocity;
+                    mCurrVelocity = velocity;
+                    notifyEdgeReached(start, max, over);
+                }
+            } else {
+                if (mStart < min) {
+                    if (mStart <= min - over) {
+                        springback(min - over, min, max);
+                    } else {
+                        // Make sure the deceleration brings us back to edge
+                        mVelocity = velocity < 0 ? velocity : -velocity;
+                        mCurrVelocity = velocity;
+                        notifyEdgeReached(start, min, over);
+                    }
+                }
             }
-            if (newY > mMaximumY) {
-                mMaximumY = newY;
+        }
+
+        void notifyEdgeReached(int start, int end, int over) {
+            // Compute post-edge deceleration
+            mState = TO_BOUNDARY;
+            mDeceleration = getDeceleration(mVelocity);
+
+            // Local time, used to compute edge crossing time.
+            float timeCurrent = mCurrVelocity / mDeceleration;
+            final int distance = end - start;
+            float timeEdge = -(float) Math.sqrt((2.0f * distance / mDeceleration)
+                             + (timeCurrent * timeCurrent));
+
+            mVelocity = (int) (mDeceleration * timeEdge);
+
+            // Simulate a symmetric bounce that started from edge
+            mStart = end;
+
+            mOver = over;
+
+            long time = AnimationUtils.currentAnimationTimeMillis();
+            mStartTime = (int) (time - 1000.0f * (timeCurrent - timeEdge));
+
+            onEdgeReached();
+        }
+
+        void onEdgeReached() {
+            // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached.
+            mState = TO_BOUNDARY;
+            mDeceleration = getDeceleration(mVelocity);
+
+            int distance = Math.round((mVelocity * mVelocity) / (2.0f * mDeceleration));
+
+            if (Math.abs(distance) < mOver) {
+                // Deceleration 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
+                mFinal = mStart + (mVelocity > 0 ? mOver : -mOver);
+                mDuration = computeDuration(mStart, mFinal, mVelocity, mDeceleration);
             }
-            mDefaultScroller.setFinalY(newY);
+        }
+
+        @Override
+        boolean continueWhenFinished() {
+            switch (mState) {
+                case TO_EDGE:
+                    // Duration from start to null velocity
+                    int duration = (int) (-1000.0f * mVelocity / mDeceleration);
+                    if (mDuration < duration) {
+                        // If the animation was clamped, we reached the edge
+                        mStart = mFinal;
+                        // Speed when edge was reached
+                        mVelocity = (int) (mVelocity + mDeceleration * mDuration / 1000.0f);
+                        mStartTime += mDuration;
+                        onEdgeReached();
+                    } else {
+                        // Normal stop, no need to continue
+                        return false;
+                    }
+                    break;
+                case TO_BOUNDARY:
+                    mStartTime += mDuration;
+                    mStart = mFinal;
+                    mFinal = mStart - (mVelocity > 0 ? mOver : -mOver);
+                    mVelocity = 0;
+                    mDuration = (int) (1000.0f * Math.sqrt(Math.abs(2.0f * mOver / mDeceleration)));
+                    mState = TO_BOUNCE;
+                    break;
+                case TO_BOUNCE:
+                    float edgeVelocity = mVelocity + mDeceleration * mDuration / 1000.0f;
+                    mVelocity = (int) (-edgeVelocity * BOUNCE_COEFFICIENT);
+                    if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) {
+                        return false;
+                    }
+                    mStart = mFinal;
+                    mStartTime += mDuration;
+                    mDuration = (int) (-2000.0f * mVelocity / mDeceleration);
+                    break;
+            }
+
+            update();
+            return true;
         }
     }
 }
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java
index 68c0ff0..239c5f4 100644
--- a/core/java/android/widget/ScrollView.java
+++ b/core/java/android/widget/ScrollView.java
@@ -1272,7 +1272,7 @@
      * Fling the scroll view
      *
      * @param velocityY The initial velocity in the Y direction. Positive
-     *                  numbers mean that the finger/curor is moving down the screen,
+     *                  numbers mean that the finger/cursor is moving down the screen,
      *                  which means we want to scroll towards the top.
      */
     public void fling(int velocityY) {
@@ -1307,6 +1307,7 @@
      *
      * <p>This version also clamps the scrolling to the bounds of our child.
      */
+    @Override
     public void scrollTo(int x, int y) {
         // we rely on the fact the View.scrollBy calls scrollTo.
         if (getChildCount() > 0) {
diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java
index 11dab02..542866a 100644
--- a/core/java/android/widget/Scroller.java
+++ b/core/java/android/widget/Scroller.java
@@ -16,8 +16,10 @@
 
 package android.widget;
 
+
 import android.content.Context;
 import android.hardware.SensorManager;
+import android.util.FloatMath;
 import android.view.ViewConfiguration;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
@@ -25,48 +27,34 @@
 
 /**
  * This class encapsulates scrolling.  The duration of the scroll
- * can be passed in the constructor and specifies the maximum time that
- * the scrolling animation should take.  Past this time, the scrolling is 
- * automatically moved to its final stage and computeScrollOffset()
- * will always return false to indicate that scrolling is over.
+ * is either specified along with the distance or depends on the initial fling velocity.
+ * Past this time, the scrolling is automatically moved to its final stage and
+ * computeScrollOffset() will always return false to indicate that scrolling is over.
  */
 public class Scroller  {
-    private int mMode;
+    int mMode;
 
-    private int mStartX;
-    private int mStartY;
-    private int mFinalX;
-    private int mFinalY;
+    MagneticScroller mScrollerX;
+    MagneticScroller mScrollerY;
 
-    private int mMinX;
-    private int mMaxX;
-    private int mMinY;
-    private int mMaxY;
+    private final Interpolator mInterpolator;
 
-    private int mCurrX;
-    private int mCurrY;
-    private long mStartTime;
-    private int mDuration;
-    private float mDurationReciprocal;
-    private float mDeltaX;
-    private float mDeltaY;
-    private float mViscousFluidScale;
-    private float mViscousFluidNormalize;
-    private boolean mFinished;
-    private Interpolator mInterpolator;
+    static final int DEFAULT_DURATION = 250;
+    static final int SCROLL_MODE = 0;
+    static final int FLING_MODE = 1;
 
-    private float mCoeffX = 0.0f;
-    private float mCoeffY = 1.0f;
-    private float mVelocity;
+    // This controls the viscous fluid effect (how much of it)
+    private final static float VISCOUS_FLUID_SCALE = 8.0f;
+    private static float VISCOUS_FLUID_NORMALIZE;
 
-    private static final int DEFAULT_DURATION = 250;
-    private static final int SCROLL_MODE = 0;
-    private static final int FLING_MODE = 1;
-
-    private final float mDeceleration;
+    static {
+        // Set a neutral value that will be used in the next call to viscousFluid().
+        VISCOUS_FLUID_NORMALIZE = 1.0f;
+        VISCOUS_FLUID_NORMALIZE = 1.0f / viscousFluid(1.0f);
+    }
 
     /**
-     * Create a Scroller with the default duration and interpolator.
+     * Create a Scroller with a viscous fluid scroll interpolator.
      */
     public Scroller(Context context) {
         this(context, null);
@@ -77,15 +65,17 @@
      * null, the default (viscous) interpolator will be used.
      */
     public Scroller(Context context, Interpolator interpolator) {
-        mFinished = true;
+        instantiateScrollers();
+        MagneticScroller.initializeFromContext(context);
+
         mInterpolator = interpolator;
-        float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
-        mDeceleration = SensorManager.GRAVITY_EARTH   // g (m/s^2)
-                      * 39.37f                        // inch/meter
-                      * ppi                           // pixels per inch
-                      * ViewConfiguration.getScrollFriction();
     }
     
+    void instantiateScrollers() {
+        mScrollerX = new MagneticScroller();
+        mScrollerY = new MagneticScroller();        
+    }
+
     /**
      * 
      * Returns whether the scroller has finished scrolling.
@@ -93,150 +83,148 @@
      * @return True if the scroller has finished scrolling, false otherwise.
      */
     public final boolean isFinished() {
-        return mFinished;
+        return mScrollerX.mFinished && mScrollerY.mFinished;
     }
-    
+
     /**
      * Force the finished field to a particular value.
-     *  
+     * 
      * @param finished The new finished value.
      */
     public final void forceFinished(boolean finished) {
-        mFinished = finished;
+        mScrollerX.mFinished = mScrollerY.mFinished = finished;
     }
-    
+
     /**
      * Returns how long the scroll event will take, in milliseconds.
      * 
      * @return The duration of the scroll in milliseconds.
      */
     public final int getDuration() {
-        return mDuration;
+        return Math.max(mScrollerX.mDuration, mScrollerY.mDuration);
     }
-    
+
     /**
-     * Returns the current X offset in the scroll. 
+     * 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 mCurrX;
+        return mScrollerX.mCurrentPosition;
     }
-    
+
     /**
-     * Returns the current Y offset in the scroll. 
+     * 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 mCurrY;
-    }
-    
-    /**
-     * @hide
-     * Returns the current velocity.
-     *
-     * @return The original velocity less the deceleration. Result may be
-     * negative.
-     */
-    public float getCurrVelocity() {
-        return mVelocity - mDeceleration * timePassed() / 2000.0f;
+        return mScrollerY.mCurrentPosition;
     }
 
     /**
-     * Returns the start X offset in the scroll. 
+     * @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 FloatMath.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 mStartX;
+        return mScrollerX.mStart;
     }
-    
+
     /**
-     * Returns the start Y offset in the scroll. 
+     * 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 mStartY;
+        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 mFinalX;
+        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 mFinalY;
+        return mScrollerY.mFinal;
     }
 
     /**
-     * Call this when you want to know the new location.  If it returns true,
-     * the animation is not yet finished.  loc will be altered to provide the
-     * new location.
-     */ 
+     * Call this when you want to know the new location. If it returns true, the
+     * animation is not yet finished.
+     */
     public boolean computeScrollOffset() {
-        if (mFinished) {
+        if (isFinished()) {
             return false;
         }
 
-        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
-    
-        if (timePassed < mDuration) {
-            switch (mMode) {
+        switch (mMode) {
             case SCROLL_MODE:
-                float x = (float)timePassed * mDurationReciprocal;
-    
-                if (mInterpolator == null)
-                    x = viscousFluid(x); 
-                else
-                    x = mInterpolator.getInterpolation(x);
-    
-                mCurrX = mStartX + Math.round(x * mDeltaX);
-                mCurrY = mStartY + Math.round(x * mDeltaY);
-                if ((mCurrX == mFinalX) && (mCurrY == mFinalY)) {
-                    mFinished = true;
-                }
-                break;
-            case FLING_MODE:
-                float timePassedSeconds = timePassed / 1000.0f;
-                float distance = (mVelocity * timePassedSeconds)
-                        - (mDeceleration * timePassedSeconds * timePassedSeconds / 2.0f);
-                
-                mCurrX = mStartX + Math.round(distance * mCoeffX);
-                // Pin to mMinX <= mCurrX <= mMaxX
-                mCurrX = Math.min(mCurrX, mMaxX);
-                mCurrX = Math.max(mCurrX, mMinX);
-                
-                mCurrY = mStartY + Math.round(distance * mCoeffY);
-                // Pin to mMinY <= mCurrY <= mMaxY
-                mCurrY = Math.min(mCurrY, mMaxY);
-                mCurrY = Math.max(mCurrY, mMinY);
+                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;
 
-                if (mCurrX == mFinalX && mCurrY == mFinalY) {
-                    mFinished = true;
+                final int duration = mScrollerX.mDuration;
+                if (elapsedTime < duration) {
+                    float q = (float) (elapsedTime) / duration;
+
+                    if (mInterpolator == null)
+                        q = viscousFluid(q);
+                    else
+                        q = mInterpolator.getInterpolation(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;
         }
-        else {
-            mCurrX = mFinalX;
-            mCurrY = mFinalY;
-            mFinished = true;
-        }
+
         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
@@ -270,83 +258,39 @@
      */
     public void startScroll(int startX, int startY, int dx, int dy, int duration) {
         mMode = SCROLL_MODE;
-        mFinished = false;
-        mDuration = duration;
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mStartX = startX;
-        mStartY = startY;
-        mFinalX = startX + dx;
-        mFinalY = startY + dy;
-        mDeltaX = dx;
-        mDeltaY = dy;
-        mDurationReciprocal = 1.0f / (float) 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);
+        mScrollerX.startScroll(startX, dx, duration);
+        mScrollerY.startScroll(startY, dy, duration);
     }
 
     /**
-     * Start scrolling based on a fling gesture. The distance travelled will
-     * depend on the initial velocity of the fling.
+     * Start scrolling based on a fling gesture. The distance traveled will
+     * depend on the initial velocity of the fling. Velocity is slowed down by a
+     * constant deceleration until it reaches 0 or the limits are reached.
      * 
      * @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.
+     *            second.
      * @param velocityY Initial velocity of the fling (Y) measured in pixels per
-     *        second
+     *            second.
      * @param minX Minimum X value. The scroller will not scroll past this
-     *        point.
+     *            point.
      * @param maxX Maximum X value. The scroller will not scroll past this
-     *        point.
+     *            point.
      * @param minY Minimum Y value. The scroller will not scroll past this
-     *        point.
+     *            point.
      * @param maxY Maximum Y value. The scroller will not scroll past this
-     *        point.
+     *            point.
      */
     public void fling(int startX, int startY, int velocityX, int velocityY,
             int minX, int maxX, int minY, int maxY) {
         mMode = FLING_MODE;
-        mFinished = false;
-
-        float velocity = (float)Math.hypot(velocityX, velocityY);
-     
-        mVelocity = velocity;
-        mDuration = (int) (1000 * velocity / mDeceleration); // Duration is in
-                                                            // milliseconds
-        mStartTime = AnimationUtils.currentAnimationTimeMillis();
-        mStartX = startX;
-        mStartY = startY;
-
-        mCoeffX = velocity == 0 ? 1.0f : velocityX / velocity; 
-        mCoeffY = velocity == 0 ? 1.0f : velocityY / velocity;
-
-        int totalDistance = (int) ((velocity * velocity) / (2 * mDeceleration));
-        
-        mMinX = minX;
-        mMaxX = maxX;
-        mMinY = minY;
-        mMaxY = maxY;
-        
-        
-        mFinalX = startX + Math.round(totalDistance * mCoeffX);
-        // Pin to mMinX <= mFinalX <= mMaxX
-        mFinalX = Math.min(mFinalX, mMaxX);
-        mFinalX = Math.max(mFinalX, mMinX);
-        
-        mFinalY = startY + Math.round(totalDistance * mCoeffY);
-        // Pin to mMinY <= mFinalY <= mMaxY
-        mFinalY = Math.min(mFinalY, mMaxY);
-        mFinalY = Math.max(mFinalY, mMinY);
+        mScrollerX.fling(startX, velocityX, minX, maxX);
+        mScrollerY.fling(startY, velocityY, minY, maxY);
     }
-    
-    
-    
-    private float viscousFluid(float x)
-    {
-        x *= mViscousFluidScale;
+
+    private static float viscousFluid(float x) {
+        x *= VISCOUS_FLUID_SCALE;
         if (x < 1.0f) {
             x -= (1.0f - (float)Math.exp(-x));
         } else {
@@ -354,70 +298,237 @@
             x = 1.0f - (float)Math.exp(1.0f - x);
             x = start + x * (1.0f - start);
         }
-        x *= mViscousFluidNormalize;
+        x *= VISCOUS_FLUID_NORMALIZE;
         return x;
     }
-    
+
     /**
      * Stops the animation. Contrary to {@link #forceFinished(boolean)},
      * aborting the animating cause the scroller to move to the final x and y
      * position
-     *
+     * 
      * @see #forceFinished(boolean)
      */
     public void abortAnimation() {
-        mCurrX = mFinalX;
-        mCurrY = mFinalY;
-        mFinished = true;
+        mScrollerX.finish();
+        mScrollerY.finish();
     }
-    
+
     /**
      * 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)
      */
     public void extendDuration(int extend) {
-        int passed = timePassed();
-        mDuration = passed + extend;
-        mDurationReciprocal = 1.0f / (float)mDuration;
-        mFinished = false;
+        mScrollerX.extendDuration(extend);
+        mScrollerY.extendDuration(extend);
     }
 
     /**
      * Returns the time elapsed since the beginning of the scrolling.
-     *
+     * 
      * @return The elapsed time in milliseconds.
      */
     public int timePassed() {
-        return (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
+        final long time = AnimationUtils.currentAnimationTimeMillis();
+        final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime);
+        return (int) (time - startTime);
     }
 
     /**
      * 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)
      */
     public void setFinalX(int newX) {
-        mFinalX = newX;
-        mDeltaX = mFinalX - mStartX;
-        mFinished = false;
+        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)
      */
     public void setFinalY(int newY) {
-        mFinalY = newY;
-        mDeltaY = mFinalY - mStartY;
-        mFinished = false;
+        mScrollerY.setFinalPosition(newY);
+    }
+
+    static class MagneticScroller {
+        // 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;
+
+        // Whether the animation is currently in progress
+        boolean mFinished;
+
+        // Constant gravity value, used to scale deceleration
+        static float GRAVITY;
+
+        static void initializeFromContext(Context context) {
+            final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;
+            GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2)
+                    * 39.37f // inch/meter
+                    * ppi // pixels per inch
+                    * ViewConfiguration.getScrollFriction();
+        }
+
+        MagneticScroller() {
+            mFinished = true;
+        }
+
+        void updateScroll(float q) {
+            mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
+        }
+
+        /*
+         * 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 duration = time - mStartTime;
+
+            if (duration > mDuration) {
+                return false;
+            }
+
+            final float t = duration / 1000.0f;
+            mCurrVelocity = mVelocity + mDeceleration * t;
+            final float distance = mVelocity * t + mDeceleration * t * t / 2.0f;
+            mCurrentPosition = mStart + (int) distance;
+
+            return true;
+        }
+
+        /*
+         * Get a signed deceleration that will reduce the velocity.
+         */
+        float getDeceleration(int velocity) {
+            return velocity > 0 ? -GRAVITY : GRAVITY;
+        }
+
+        /*
+         * Returns the time (in milliseconds) it will take to go from start to end.
+         */
+        static int computeDuration(int start, int end, float initialVelocity, float deceleration) {
+            final int distance = start - end;
+            final float discriminant = initialVelocity * initialVelocity - 2.0f * deceleration
+                    * distance;
+            if (discriminant >= 0.0f) {
+                float delta = (float) Math.sqrt(discriminant);
+                if (deceleration < 0.0f) {
+                    delta = -delta;
+                }
+                return (int) (1000.0f * (-initialVelocity - delta) / deceleration);
+            }
+
+            // End position can not be reached
+            return 0;
+        }
+
+        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 fling(int start, int velocity, int min, int max) {
+            mFinished = false;
+
+            mStart = start;
+            mStartTime = AnimationUtils.currentAnimationTimeMillis();
+
+            mVelocity = velocity;
+
+            mDeceleration = getDeceleration(velocity);
+
+            // A start from an invalid position immediately brings back to a valid position
+            if (mStart < min) {
+                mDuration = 0;
+                mFinal = min;
+                return;
+            }
+
+            if (mStart > max) {
+                mDuration = 0;
+                mFinal = max;
+                return;
+            }
+
+            // Duration are expressed in milliseconds
+            mDuration = (int) (-1000.0f * velocity / mDeceleration);
+
+            mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration));
+
+            // Clamp to a valid final position
+            if (mFinal < min) {
+                mFinal = min;
+                mDuration = computeDuration(mStart, min, mVelocity, mDeceleration);
+            }
+
+            if (mFinal > max) {
+                mFinal = max;
+                mDuration = computeDuration(mStart, max, mVelocity, mDeceleration);
+            }
+        }
+
+        void finish() {
+            mCurrentPosition = mFinal;
+            // Not reset since WebView relies on this value for fast fling.
+            // mCurrVelocity = 0.0f;
+            mFinished = true;
+        }
+
+        boolean continueWhenFinished() {
+            return false;
+        }
+
+        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;
+        }
     }
 }
diff --git a/core/jni/android_bluetooth_ScoSocket.cpp b/core/jni/android_bluetooth_ScoSocket.cpp
index 3afe5f5..8588bc2 100644
--- a/core/jni/android_bluetooth_ScoSocket.cpp
+++ b/core/jni/android_bluetooth_ScoSocket.cpp
@@ -37,6 +37,23 @@
 #ifdef HAVE_BLUETOOTH
 #include <bluetooth/bluetooth.h>
 #include <bluetooth/sco.h>
+#include <bluetooth/hci.h>
+
+#define MAX_LINE 255
+
+/*
+ * Defines the module strings used in the blacklist file.
+ * These are used by consumers of the blacklist file to see if the line is
+ * used by that module.
+ */
+#define SCO_BLACKLIST_MODULE_NAME "scoSocket"
+
+
+/* Define the type strings used in the blacklist file. */
+#define BLACKLIST_BY_NAME "name"
+#define BLACKLIST_BY_PARTIAL_NAME "partial_name"
+#define BLACKLIST_BY_OUI "vendor_oui"
+
 #endif
 
 /* Ideally, blocking I/O on a SCO socket would return when another thread
@@ -67,11 +84,28 @@
 
 struct thread_data_t;
 static void *work_thread(void *arg);
-static int connect_work(const char *address);
+static int connect_work(const char *address, uint16_t sco_pkt_type);
 static int accept_work(int signal_sk);
 static void wait_for_close(int sk, int signal_sk);
 static void closeNative(JNIEnv *env, jobject object);
 
+static void parseBlacklist(void);
+static uint16_t getScoType(char *address, const char *name);
+
+#define COMPARE_STRING(key, s) (!strncmp(key, s, strlen(s)))
+
+/* Blacklist data */
+typedef struct scoBlacklist {
+    int fieldType;
+    char *value;
+    uint16_t scoType;
+    struct scoBlacklist *next;
+} scoBlacklist_t;
+
+#define BL_TYPE_NAME 1   // Field type is name string
+
+static scoBlacklist_t *blacklist = NULL;
+
 /* shared native data - protected by mutex */
 typedef struct {
     pthread_mutex_t mutex;
@@ -87,11 +121,144 @@
     bool is_accept;        // accept (listening) or connect (outgoing) thread
     int signal_sk;         // socket for thread to listen for unblock signal
     char address[BTADDR_SIZE];  // BT addres as string
+    uint16_t sco_pkt_type;   // SCO packet types supported
 };
 
 static inline native_data_t * get_native_data(JNIEnv *env, jobject object) {
     return (native_data_t *)(env->GetIntField(object, field_mNativeData));
 }
+
+static uint16_t str2scoType (char *key) {
+    LOGV("%s: key = %s", __FUNCTION__, key);
+    if (COMPARE_STRING(key, "ESCO_HV1"))
+        return ESCO_HV1;
+    if (COMPARE_STRING(key, "ESCO_HV2"))
+        return ESCO_HV2;
+    if (COMPARE_STRING(key, "ESCO_HV3"))
+        return ESCO_HV3;
+    if (COMPARE_STRING(key, "ESCO_EV3"))
+        return ESCO_EV3;
+    if (COMPARE_STRING(key, "ESCO_EV4"))
+        return ESCO_EV4;
+    if (COMPARE_STRING(key, "ESCO_EV5"))
+        return ESCO_EV5;
+    if (COMPARE_STRING(key, "ESCO_2EV3"))
+        return ESCO_2EV3;
+    if (COMPARE_STRING(key, "ESCO_3EV3"))
+        return ESCO_3EV3;
+    if (COMPARE_STRING(key, "ESCO_2EV5"))
+        return ESCO_2EV5;
+    if (COMPARE_STRING(key, "ESCO_3EV5"))
+        return ESCO_3EV5;
+    if (COMPARE_STRING(key, "SCO_ESCO_MASK"))
+        return SCO_ESCO_MASK;
+    if (COMPARE_STRING(key, "EDR_ESCO_MASK"))
+        return EDR_ESCO_MASK;
+    if (COMPARE_STRING(key, "ALL_ESCO_MASK"))
+        return ALL_ESCO_MASK;
+    LOGE("Unknown SCO Type (%s) skipping",key);
+    return 0;
+}
+
+static void parseBlacklist(void) {
+    const char *filename = "/etc/bluetooth/blacklist.conf";
+    char line[MAX_LINE];
+    scoBlacklist_t *list = NULL;
+    scoBlacklist_t *newelem;
+
+    LOGV(__FUNCTION__);
+
+    /* Open file */
+    FILE *fp = fopen(filename, "r");
+    if(!fp) {
+        LOGE("Error(%s)opening blacklist file", strerror(errno));
+        return;
+    }
+
+    while (fgets(line, MAX_LINE, fp) != NULL) {
+        if ((COMPARE_STRING(line, "//")) || (!strcmp(line, "")))
+            continue;
+        char *module = strtok(line,":");
+        if (COMPARE_STRING(module, SCO_BLACKLIST_MODULE_NAME)) {
+            newelem = (scoBlacklist_t *)calloc(1, sizeof(scoBlacklist_t));
+            if (newelem == NULL) {
+                LOGE("%s: out of memory!", __FUNCTION__);
+                return;
+            }
+            // parse line
+            char *type = strtok(NULL, ",");
+            char *valueList = strtok(NULL, ",");
+            char *paramList = strtok(NULL, ",");
+            if (COMPARE_STRING(type, BLACKLIST_BY_NAME)) {
+                // Extract Name from Value list
+                newelem->fieldType = BL_TYPE_NAME;
+                newelem->value = (char *)calloc(1, strlen(valueList));
+                if (newelem->value == NULL) {
+                    LOGE("%s: out of memory!", __FUNCTION__);
+                    continue;
+                }
+                valueList++;  // Skip open quote
+                strncpy(newelem->value, valueList, strlen(valueList) - 1);
+
+                // Get Sco Settings from Parameters
+                char *param = strtok(paramList, ";");
+                uint16_t scoTypes = 0;
+                while (param != NULL) {
+                    uint16_t sco;
+                    if (param[0] == '-') {
+                        param++;
+                        sco = str2scoType(param);
+                        if (sco != 0)
+                            scoTypes &= ~sco;
+                    } else if (param[0] == '+') {
+                        param++;
+                        sco = str2scoType(param);
+                        if (sco != 0)
+                            scoTypes |= sco;
+                    } else if (param[0] == '=') {
+                        param++;
+                        sco = str2scoType(param);
+                        if (sco != 0)
+                            scoTypes = sco;
+                    } else {
+                        LOGE("Invalid SCO type must be =, + or -");
+                    }
+                    param = strtok(NULL, ";");
+                }
+                newelem->scoType = scoTypes;
+            } else {
+                LOGE("Unknown SCO type entry in Blacklist file");
+                continue;
+            }
+            if (list) {
+                list->next = newelem;
+                list = newelem;
+            } else {
+                blacklist = list = newelem;
+            }
+            LOGI("Entry name = %s ScoTypes = 0x%x", newelem->value,
+                 newelem->scoType);
+        }
+    }
+    fclose(fp);
+    return;
+}
+static uint16_t getScoType(char *address, const char *name) {
+    uint16_t ret = 0;
+    scoBlacklist_t *list = blacklist;
+
+    while (list != NULL) {
+        if (list->fieldType == BL_TYPE_NAME) {
+            if (COMPARE_STRING(name, list->value)) {
+                ret = list->scoType;
+                break;
+            }
+        }
+        list = list->next;
+    }
+    LOGI("%s %s - 0x%x",  __FUNCTION__, name, ret);
+    return ret;
+}
 #endif
 
 static void classInitNative(JNIEnv* env, jclass clazz) {
@@ -104,6 +271,9 @@
     method_onAccepted = env->GetMethodID(clazz, "onAccepted", "(I)V");
     method_onConnected = env->GetMethodID(clazz, "onConnected", "(I)V");
     method_onClosed = env->GetMethodID(clazz, "onClosed", "()V");
+
+    /* Read the blacklist file in here */
+    parseBlacklist();
 #endif
 }
 
@@ -192,7 +362,9 @@
     return JNI_FALSE;
 }
 
-static jboolean connectNative(JNIEnv *env, jobject object, jstring address) {
+static jboolean connectNative(JNIEnv *env, jobject object, jstring address,
+        jstring name) {
+
     LOGV(__FUNCTION__);
 #ifdef HAVE_BLUETOOTH
     native_data_t *nat = get_native_data(env, object);
@@ -200,6 +372,7 @@
     pthread_t thread;
     struct thread_data_t *data;
     const char *c_address;
+    const char *c_name;
 
     pthread_mutex_lock(&nat->mutex);
     if (nat->signal_sk != -1) {
@@ -231,6 +404,11 @@
     env->ReleaseStringUTFChars(address, c_address);
     data->is_accept = false;
 
+    c_name = env->GetStringUTFChars(name, NULL);
+    /* See if this device is in the black list */
+    data->sco_pkt_type = getScoType(data->address, c_name);
+    env->ReleaseStringUTFChars(name, c_name);
+
     if (pthread_create(&thread, NULL, &work_thread, (void *)data) < 0) {
         LOGE("%s: pthread_create() failed: %s", __FUNCTION__, strerror(errno));
         return JNI_FALSE;
@@ -282,7 +460,7 @@
         sk = accept_work(data->signal_sk);
         LOGV("SCO OBJECT %p END ACCEPT *****", data->nat->object);
     } else {
-        sk = connect_work(data->address);
+        sk = connect_work(data->address, data->sco_pkt_type);
     }
 
     /* callback with connection result */
@@ -426,7 +604,7 @@
     return -1;
 }
 
-static int connect_work(const char *address) {
+static int connect_work(const char *address, uint16_t sco_pkt_type) {
     LOGV(__FUNCTION__);
     struct sockaddr_sco addr;
     int sk = -1;
@@ -449,6 +627,7 @@
     memset(&addr, 0, sizeof(addr));
     addr.sco_family = AF_BLUETOOTH;
     get_bdaddr(address, &addr.sco_bdaddr);
+    addr.sco_pkt_type = sco_pkt_type;
     LOGI("Connecting to socket");
     while (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
         if (errno != EINTR) {
@@ -493,7 +672,7 @@
     {"classInitNative", "()V", (void*)classInitNative},
     {"initNative", "()V", (void *)initNative},
     {"destroyNative", "()V", (void *)destroyNative},
-    {"connectNative", "(Ljava/lang/String;)Z", (void *)connectNative},
+    {"connectNative", "(Ljava/lang/String;Ljava/lang/String;)Z", (void *)connectNative},
     {"acceptNative", "()Z", (void *)acceptNative},
     {"closeNative", "()V", (void *)closeNative},
 };
diff --git a/core/res/res/drawable/pattern_underwear.png b/core/res/res/drawable/pattern_underwear.png
deleted file mode 100644
index 651212f..0000000
--- a/core/res/res/drawable/pattern_underwear.png
+++ /dev/null
Binary files differ
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index 473f580..0016503 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -935,17 +935,27 @@
             mCallingPid = tr.sender_pid;
             mCallingUid = tr.sender_euid;
             
-            bool doBackground = !gDisableBackgroundScheduling &&
-                    getpriority(PRIO_PROCESS, mMyThreadId)
-                            >= ANDROID_PRIORITY_BACKGROUND;
-            if (doBackground) {
-                // We have inherited a background priority from the caller.
-                // Ensure this thread is in the background scheduling class,
-                // since the driver won't modify scheduling classes for us.
-                androidSetThreadSchedulingGroup(mMyThreadId,
-                        ANDROID_TGROUP_BG_NONINTERACT);
+            int curPrio = getpriority(PRIO_PROCESS, mMyThreadId);
+            if (gDisableBackgroundScheduling) {
+                if (curPrio > ANDROID_PRIORITY_NORMAL) {
+                    // We have inherited a reduced priority from the caller, but do not
+                    // want to run in that state in this process.  The driver set our
+                    // priority already (though not our scheduling class), so bounce
+                    // it back to the default before invoking the transaction.
+                    setpriority(PRIO_PROCESS, mMyThreadId, ANDROID_PRIORITY_NORMAL);
+                }
+            } else {
+                if (curPrio >= ANDROID_PRIORITY_BACKGROUND) {
+                    // We want to use the inherited priority from the caller.
+                    // Ensure this thread is in the background scheduling class,
+                    // since the driver won't modify scheduling classes for us.
+                    // The scheduling group is reset to default by the caller
+                    // once this method returns after the transaction is complete.
+                    androidSetThreadSchedulingGroup(mMyThreadId,
+                                                    ANDROID_TGROUP_BG_NONINTERACT);
+                }
             }
-            
+
             //LOGI(">>>> TRANSACT from pid %d uid %d\n", mCallingPid, mCallingUid);
             
             Parcel reply;
@@ -982,14 +992,7 @@
             
             mCallingPid = origPid;
             mCallingUid = origUid;
-            
-            if (doBackground) {
-                // We moved to the background scheduling group to execute
-                // this transaction, so now that we are done go back in the
-                // foreground.
-                androidSetThreadSchedulingGroup(mMyThreadId, ANDROID_TGROUP_DEFAULT);
-            }
-            
+
             IF_LOG_TRANSACTIONS() {
                 TextOutput::Bundle _b(alog);
                 alog << "BC_REPLY thr " << (void*)pthread_self() << " / obj "
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index e9bcafe..f845fec1 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -634,12 +634,12 @@
             } else if (MediaFile.isImageFileType(mFileType)) {
                 // FIXME - add DESCRIPTION
             } else if (MediaFile.isAudioFileType(mFileType)) {
-                String artist = mArtist != null && mArtist.length() > 0 ?
-                        mArtist : MediaStore.UNKNOWN_STRING;
-                map.put(Audio.Media.ARTIST, artist);
-                map.put(Audio.Media.ALBUM_ARTIST, mAlbumArtist != null &&
-                        mAlbumArtist.length() > 0 ? mAlbumArtist : artist);
-                map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaStore.UNKNOWN_STRING));
+                map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
+                        mArtist : MediaStore.UNKNOWN_STRING);
+                map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
+                        mAlbumArtist.length() > 0) ? mAlbumArtist : null);
+                map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
+                        mAlbum : MediaStore.UNKNOWN_STRING);
                 map.put(Audio.Media.COMPOSER, mComposer);
                 if (mYear != 0) {
                     map.put(Audio.Media.YEAR, mYear);
diff --git a/test-runner/src/android/test/InstrumentationTestRunner.java b/test-runner/src/android/test/InstrumentationTestRunner.java
index ee6b89c..63d50c7 100644
--- a/test-runner/src/android/test/InstrumentationTestRunner.java
+++ b/test-runner/src/android/test/InstrumentationTestRunner.java
@@ -121,6 +121,10 @@
  * -e class com.android.foo.FooTest,com.android.foo.TooTest
  * com.android.foo/android.test.InstrumentationTestRunner
  * <p/>
+ * <b>Running all tests in a java package:</b> adb shell am instrument -w
+ * -e package com.android.foo.subpkg
+ *  com.android.foo/android.test.InstrumentationTestRunner
+ * <p/>
  * <b>Including performance tests:</b> adb shell am instrument -w
  * -e perf true
  * com.android.foo/android.test.InstrumentationTestRunner