Merge "Add KDDI/Softbank to available Shift_Jis mapping."
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 1d9e0f1..2ead976 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -1657,7 +1657,7 @@
                 }
                 boolean needsProvisioning;
                 try {
-                    needsProvisioning = telephony.getCdmaNeedsProvisioning();
+                    needsProvisioning = telephony.needsOtaServiceProvisioning();
                 } catch (RemoteException e) {
                     Log.w(TAG, "exception while checking provisioning", e);
                     // default to NOT wiping out the passwords
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index c809b5a..eb36b5d 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -28,6 +28,7 @@
 import android.graphics.drawable.Drawable;
 import android.text.Editable;
 import android.text.InputFilter;
+import android.text.Layout;
 import android.text.Selection;
 import android.text.Spannable;
 import android.text.TextPaint;
@@ -497,9 +498,10 @@
             // to big for the case of a small textfield.
             int smallerSlop = slop/2;
             if (dx > smallerSlop || dy > smallerSlop) {
-                if (mWebView != null) {
-                    float maxScrollX = (float) Touch.getMaxScrollX(this,
-                                getLayout(), mScrollY);
+                Layout layout = getLayout();
+                if (mWebView != null && layout != null) {
+                    float maxScrollX = (float) Touch.getMaxScrollX(this, layout,
+                            mScrollY);
                     if (DebugFlags.WEB_TEXT_VIEW) {
                         Log.v(LOGTAG, "onTouchEvent x=" + mScrollX + " y="
                                 + mScrollY + " maxX=" + maxScrollX);
@@ -936,14 +938,4 @@
     /* package */ void updateCachedTextfield() {
         mWebView.updateCachedTextfield(getText().toString());
     }
-
-    @Override
-    public boolean requestRectangleOnScreen(Rect rectangle) {
-        // don't scroll while in zoom animation. When it is done, we will adjust
-        // the WebTextView if it is in editing mode.
-        if (!mWebView.inAnimateZoom()) {
-            return super.requestRectangleOnScreen(rectangle);
-        }
-        return false;
-    }
 }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index ebcf5c5..5a7e78b 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -44,7 +44,6 @@
 import android.text.IClipboard;
 import android.text.Selection;
 import android.text.Spannable;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.EventLog;
 import android.util.Log;
@@ -61,7 +60,6 @@
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityManager;
-import android.view.animation.AlphaAnimation;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
@@ -77,7 +75,6 @@
 import android.widget.ListView;
 import android.widget.Scroller;
 import android.widget.Toast;
-import android.widget.ZoomButtonsController;
 import android.widget.AdapterView.OnItemClickListener;
 
 import java.io.File;
@@ -649,32 +646,6 @@
     // initial scale in percent. 0 means using default.
     private int mInitialScaleInPercent = 0;
 
-    // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
-    // engadget always have wider mContentWidth no matter what viewport size is.
-    int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH;
-    float mTextWrapScale;
-
-    // default scale. Depending on the display density.
-    static int DEFAULT_SCALE_PERCENT;
-    private float mDefaultScale;
-
-    private static float MINIMUM_SCALE_INCREMENT = 0.01f;
-
-    // set to true temporarily during ScaleGesture triggered zoom
-    private boolean mPreviewZoomOnly = false;
-
-    // computed scale and inverse, from mZoomWidth.
-    private float mActualScale;
-    private float mInvActualScale;
-    // if this is non-zero, it is used on drawing rather than mActualScale
-    private float mZoomScale;
-    private float mInvInitialZoomScale;
-    private float mInvFinalZoomScale;
-    private int mInitialScrollX;
-    private int mInitialScrollY;
-    private long mZoomStart;
-    private static final int ZOOM_ANIMATION_LENGTH = 500;
-
     private boolean mUserScroll = false;
 
     private int mSnapScrollMode = SNAP_NONE;
@@ -815,11 +786,6 @@
         }
     }
 
-    // These keep track of the center point of the zoom.  They are used to
-    // determine the point around which we should zoom.
-    private float mZoomCenterX;
-    private float mZoomCenterY;
-
     /**
      * Construct a new WebView with a Context object.
      * @param context A Context object used to access application assets.
@@ -875,7 +841,7 @@
         mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces);
         mDatabase = WebViewDatabase.getInstance(context);
         mScroller = new Scroller(context);
-        mZoomManager = new ZoomManager(this);
+        mZoomManager = new ZoomManager(this, mCallbackProxy);
 
         /* The init method must follow the creation of certain member variables,
          * such as the mZoomManager.
@@ -914,12 +880,6 @@
         // use one line height, 16 based on our current default font, for how
         // far we allow a touch be away from the edge of a link
         mNavSlop = (int) (16 * density);
-        // density adjusted scale factors
-        DEFAULT_SCALE_PERCENT = (int) (100 * density);
-        mDefaultScale = density;
-        mActualScale = density;
-        mInvActualScale = 1 / density;
-        mTextWrapScale = density;
         mZoomManager.init(density);
         mMaximumFling = configuration.getScaledMaximumFlingVelocity();
     }
@@ -945,20 +905,10 @@
     }
 
     /* package */void updateDefaultZoomDensity(int zoomDensity) {
-        final float density = getContext().getResources().getDisplayMetrics().density
+        final float density = mContext.getResources().getDisplayMetrics().density
                 * 100 / zoomDensity;
-        if (Math.abs(density - mDefaultScale) > 0.01) {
-            float scaleFactor = density / mDefaultScale;
-            // adjust the limits
-            mNavSlop = (int) (16 * density);
-            DEFAULT_SCALE_PERCENT = (int) (100 * density);
-            mZoomManager.DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
-            mZoomManager.DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
-            mDefaultScale = density;
-            mZoomManager.mMaxZoomScale *= scaleFactor;
-            mZoomManager.mMinZoomScale *= scaleFactor;
-            setNewZoomScale(mActualScale * scaleFactor, true, false);
-        }
+        mNavSlop = (int) (16 * density);
+        mZoomManager.updateDefaultZoomDensity(density);
     }
 
     /* package */ boolean onSavePassword(String schemePlusHost, String username,
@@ -1075,7 +1025,7 @@
      * returns the height of the titlebarview (if any). Does not care about
      * scrolling
      */
-    private int getTitleHeight() {
+    int getTitleHeight() {
         return mTitleBar != null ? mTitleBar.getHeight() : 0;
     }
 
@@ -1336,8 +1286,8 @@
         // now update the bundle
         b.putInt("scrollX", mScrollX);
         b.putInt("scrollY", mScrollY);
-        b.putFloat("scale", mActualScale);
-        b.putFloat("textwrapScale", mTextWrapScale);
+        b.putFloat("scale", mZoomManager.mActualScale);
+        b.putFloat("textwrapScale", mZoomManager.mTextWrapScale);
         b.putBoolean("overview", mZoomManager.mInZoomOverview);
         return true;
     }
@@ -1355,9 +1305,9 @@
         // as getWidth() / getHeight() of the view are not available yet, set up
         // mActualScale, so that when onSizeChanged() is called, the rest will
         // be set correctly
-        mActualScale = scale;
-        mInvActualScale = 1 / scale;
-        mTextWrapScale = b.getFloat("textwrapScale", scale);
+        mZoomManager.mActualScale = scale;
+        mZoomManager.mInvActualScale = 1 / scale;
+        mZoomManager.mTextWrapScale = b.getFloat("textwrapScale", scale);
         mZoomManager.mInZoomOverview = b.getBoolean("overview");
         invalidate();
     }
@@ -1778,7 +1728,7 @@
      * @return The current scale.
      */
     public float getScale() {
-        return mActualScale;
+        return mZoomManager.mActualScale;
     }
 
     /**
@@ -1936,12 +1886,12 @@
     }
 
     // Expects x in view coordinates
-    private int pinLocX(int x) {
+    int pinLocX(int x) {
         return pinLoc(x, getViewWidth(), computeHorizontalScrollRange());
     }
 
     // Expects y in view coordinates
-    private int pinLocY(int y) {
+    int pinLocY(int y) {
         return pinLoc(y, getViewHeightWithTitle(),
                       computeVerticalScrollRange() + getTitleHeight());
     }
@@ -1991,7 +1941,7 @@
      * height.
      */
     private int viewToContentDimension(int d) {
-        return Math.round(d * mInvActualScale);
+        return Math.round(d * mZoomManager.mInvActualScale);
     }
 
     /**
@@ -2017,7 +1967,7 @@
      * Returns the result as a float.
      */
     private float viewToContentXf(int x) {
-        return x * mInvActualScale;
+        return x * mZoomManager.mInvActualScale;
     }
 
     /**
@@ -2026,7 +1976,7 @@
      * embedded into the WebView. Returns the result as a float.
      */
     private float viewToContentYf(int y) {
-        return (y - getTitleHeight()) * mInvActualScale;
+        return (y - getTitleHeight()) * mZoomManager.mInvActualScale;
     }
 
     /**
@@ -2036,7 +1986,7 @@
      * height.
      */
     /*package*/ int contentToViewDimension(int d) {
-        return Math.round(d * mActualScale);
+        return Math.round(d * mZoomManager.mActualScale);
     }
 
     /**
@@ -2077,7 +2027,7 @@
     // Called by JNI to invalidate the View, given rectangle coordinates in
     // content space
     private void viewInvalidate(int l, int t, int r, int b) {
-        final float scale = mActualScale;
+        final float scale = mZoomManager.mActualScale;
         final int dy = getTitleHeight();
         invalidate((int)Math.floor(l * scale),
                    (int)Math.floor(t * scale) + dy,
@@ -2088,7 +2038,7 @@
     // Called by JNI to invalidate the View after a delay, given rectangle
     // coordinates in content space
     private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) {
-        final float scale = mActualScale;
+        final float scale = mZoomManager.mActualScale;
         final int dy = getTitleHeight();
         postInvalidateDelayed(delay,
                               (int)Math.floor(l * scale),
@@ -2126,13 +2076,7 @@
             // updated when we get out of that mode.
             if (!mDrawHistory) {
                 // repin our scroll, taking into account the new content size
-                int oldX = mScrollX;
-                int oldY = mScrollY;
-                mScrollX = pinLocX(mScrollX);
-                mScrollY = pinLocY(mScrollY);
-                if (oldX != mScrollX || oldY != mScrollY) {
-                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-                }
+                updateScrollCoordinates(pinLocX(mScrollX), pinLocY(mScrollY));
                 if (!mScroller.isFinished()) {
                     // We are in the middle of a scroll.  Repin the final scroll
                     // position.
@@ -2144,79 +2088,12 @@
         contentSizeChanged(updateLayout);
     }
 
-    private void setNewZoomScale(float scale, boolean updateTextWrapScale,
-            boolean force) {
-        if (scale < mZoomManager.mMinZoomScale) {
-            scale = mZoomManager.mMinZoomScale;
-            // set mInZoomOverview for non mobile sites
-            if (scale < mDefaultScale) {
-                mZoomManager.mInZoomOverview = true;
-            }
-        } else if (scale > mZoomManager.mMaxZoomScale) {
-            scale = mZoomManager.mMaxZoomScale;
-        }
-        if (updateTextWrapScale) {
-            mTextWrapScale = scale;
-            // reset mLastHeightSent to force VIEW_SIZE_CHANGED sent to WebKit
-            mLastHeightSent = 0;
-        }
-        if (scale != mActualScale || force) {
-            if (mDrawHistory) {
-                // If history Picture is drawn, don't update scroll. They will
-                // be updated when we get out of that mode.
-                if (scale != mActualScale && !mPreviewZoomOnly) {
-                    mCallbackProxy.onScaleChanged(mActualScale, scale);
-                }
-                mActualScale = scale;
-                mInvActualScale = 1 / scale;
-                sendViewSizeZoom();
-            } else {
-                // update our scroll so we don't appear to jump
-                // i.e. keep the center of the doc in the center of the view
-
-                int oldX = mScrollX;
-                int oldY = mScrollY;
-                float ratio = scale * mInvActualScale;   // old inverse
-                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
-                float sy = ratio * oldY + (ratio - 1)
-                        * (mZoomCenterY - getTitleHeight());
-
-                // now update our new scale and inverse
-                if (scale != mActualScale && !mPreviewZoomOnly) {
-                    mCallbackProxy.onScaleChanged(mActualScale, scale);
-                }
-                mActualScale = scale;
-                mInvActualScale = 1 / scale;
-
-                // Scale all the child views
-                mViewManager.scaleAll();
-
-                // as we don't have animation for scaling, don't do animation
-                // for scrolling, as it causes weird intermediate state
-                //        pinScrollTo(Math.round(sx), Math.round(sy));
-                mScrollX = pinLocX(Math.round(sx));
-                mScrollY = pinLocY(Math.round(sy));
-
-                // update webkit
-                if (oldX != mScrollX || oldY != mScrollY) {
-                    onScrollChanged(mScrollX, mScrollY, oldX, oldY);
-                } else {
-                    // the scroll position is adjusted at the beginning of the
-                    // zoom animation. But we want to update the WebKit at the
-                    // end of the zoom animation. See comments in onScaleEnd().
-                    sendOurVisibleRect();
-                }
-                sendViewSizeZoom();
-            }
-        }
-    }
-
     // Used to avoid sending many visible rect messages.
     private Rect mLastVisibleRectSent;
     private Rect mLastGlobalRect;
 
-    private Rect sendOurVisibleRect() {
-        if (mPreviewZoomOnly) return mLastVisibleRectSent;
+    Rect sendOurVisibleRect() {
+        if (mZoomManager.mPreviewZoomOnly) return mLastVisibleRectSent;
 
         Rect rect = new Rect();
         calcOurContentVisibleRect(rect);
@@ -2283,6 +2160,11 @@
         r.bottom = Math.min(viewToContentYf(ri.bottom), (float)mContentHeight);
     }
 
+    void setViewSizeAnchor(int x, int y) {
+        mAnchorX = x;
+        mAnchorY = y;
+    }
+
     static class ViewSizeData {
         int mWidth;
         int mHeight;
@@ -2295,16 +2177,19 @@
 
     /**
      * Compute unzoomed width and height, and if they differ from the last
-     * values we sent, send them to webkit (to be used has new viewport)
+     * values we sent, send them to webkit (to be used as new viewport)
+     *
+     * @param force ensures that the message is sent to webkit even if the width
+     * or height has not changed since the last message
      *
      * @return true if new values were sent
      */
-    private boolean sendViewSizeZoom() {
-        if (mPreviewZoomOnly) return false;
+    boolean sendViewSizeZoom(boolean force) {
+        if (mZoomManager.mPreviewZoomOnly) return false;
 
         int viewWidth = getViewWidth();
-        int newWidth = Math.round(viewWidth * mInvActualScale);
-        int newHeight = Math.round(getViewHeight() * mInvActualScale);
+        int newWidth = Math.round(viewWidth * mZoomManager.mInvActualScale);
+        int newHeight = Math.round(getViewHeight() * mZoomManager.mInvActualScale);
         /*
          * Because the native side may have already done a layout before the
          * View system was able to measure us, we have to send a height of 0 to
@@ -2317,13 +2202,13 @@
             newHeight = 0;
         }
         // Avoid sending another message if the dimensions have not changed.
-        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) {
+        if (newWidth != mLastWidthSent || newHeight != mLastHeightSent || force) {
             ViewSizeData data = new ViewSizeData();
             data.mWidth = newWidth;
             data.mHeight = newHeight;
-            data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);;
-            data.mScale = mActualScale;
-            data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure;
+            data.mTextWrapWidth = Math.round(viewWidth / mZoomManager.mTextWrapScale);
+            data.mScale = mZoomManager.mActualScale;
+            data.mIgnoreHeight = mZoomManager.isZoomAnimating() && !mHeightCanMeasure;
             data.mAnchorX = mAnchorX;
             data.mAnchorY = mAnchorY;
             mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
@@ -2340,12 +2225,12 @@
         if (mDrawHistory) {
             return mHistoryWidth;
         } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF
-                && (mActualScale - mZoomManager.mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) {
+                && mZoomManager.isZoomedOut()) {
             // only honor the scrollbar mode when it is at minimum zoom level
             return computeHorizontalScrollExtent();
         } else {
             // to avoid rounding error caused unnecessary scrollbar, use floor
-            return (int) Math.floor(mContentWidth * mActualScale);
+            return (int) Math.floor(mContentWidth * mZoomManager.mActualScale);
         }
     }
 
@@ -2354,12 +2239,12 @@
         if (mDrawHistory) {
             return mHistoryHeight;
         } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF
-                && (mActualScale - mZoomManager.mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) {
+                && mZoomManager.isZoomedOut()) {
             // only honor the scrollbar mode when it is at minimum zoom level
             return computeVerticalScrollExtent();
         } else {
             // to avoid rounding error caused unnecessary scrollbar, use floor
-            return (int) Math.floor(mContentHeight * mActualScale);
+            return (int) Math.floor(mContentHeight * mZoomManager.mActualScale);
         }
     }
 
@@ -2960,7 +2845,7 @@
         } else {
             // If we don't request a layout, try to send our view size to the
             // native side to ensure that WebCore has the correct dimensions.
-            sendViewSizeZoom();
+            sendViewSizeZoom(false);
         }
     }
 
@@ -3263,10 +3148,6 @@
         }
     }
 
-    boolean inAnimateZoom() {
-        return mZoomScale != 0;
-    }
-
     /**
      * Need to adjust the WebTextView after a change in zoom, since mActualScale
      * has changed.  This is especially important for password fields, which are
@@ -3316,12 +3197,12 @@
     private void drawCoreAndCursorRing(Canvas canvas, int color,
         boolean drawCursorRing) {
         if (mDrawHistory) {
-            canvas.scale(mActualScale, mActualScale);
+            canvas.scale(mZoomManager.mActualScale, mZoomManager.mActualScale);
             canvas.drawPicture(mHistoryPicture);
             return;
         }
 
-        boolean animateZoom = mZoomScale != 0;
+        boolean animateZoom = mZoomManager.isZoomAnimating();
         boolean animateScroll = ((!mScroller.isFinished()
                 || mVelocityTracker != null)
                 && (mTouchMode != TOUCH_DRAG_MODE ||
@@ -3341,16 +3222,16 @@
         }
         if (animateZoom) {
             float zoomScale;
-            int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
-            if (interval < ZOOM_ANIMATION_LENGTH) {
-                float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
-                zoomScale = 1.0f / (mInvInitialZoomScale
-                        + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
+            int interval = (int) (SystemClock.uptimeMillis() - mZoomManager.mZoomStart);
+            if (interval < mZoomManager.ZOOM_ANIMATION_LENGTH) {
+                float ratio = (float) interval / mZoomManager.ZOOM_ANIMATION_LENGTH;
+                zoomScale = 1.0f / (mZoomManager.mInvInitialZoomScale
+                        + (mZoomManager.mInvFinalZoomScale - mZoomManager.mInvInitialZoomScale) * ratio);
                 invalidate();
             } else {
-                zoomScale = mZoomScale;
+                zoomScale = mZoomManager.mZoomScale;
                 // set mZoomScale to be 0 as we have done animation
-                mZoomScale = 0;
+                mZoomManager.mZoomScale = 0;
                 WebViewCore.resumeUpdatePicture(mWebViewCore);
                 // call invalidate() again to draw with the final filters
                 invalidate();
@@ -3366,22 +3247,22 @@
             }
             // calculate the intermediate scroll position. As we need to use
             // zoomScale, we can't use pinLocX/Y directly. Copy the logic here.
-            float scale = zoomScale * mInvInitialZoomScale;
-            int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX)
-                    - mZoomCenterX);
+            float scale = zoomScale * mZoomManager.mInvInitialZoomScale;
+            int tx = Math.round(scale * (mZoomManager.mInitialScrollX + mZoomManager.mZoomCenterX)
+                    - mZoomManager.mZoomCenterX);
             tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth
                     * zoomScale)) + mScrollX;
             int titleHeight = getTitleHeight();
             int ty = Math.round(scale
-                    * (mInitialScrollY + mZoomCenterY - titleHeight)
-                    - (mZoomCenterY - titleHeight));
+                    * (mZoomManager.mInitialScrollY + mZoomManager.mZoomCenterY - titleHeight)
+                    - (mZoomManager.mZoomCenterY - titleHeight));
             ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty
                     - titleHeight, getViewHeight(), Math.round(mContentHeight
                     * zoomScale)) + titleHeight) + mScrollY;
             canvas.translate(tx, ty);
             canvas.scale(zoomScale, zoomScale);
             if (inEditingMode() && !mNeedToAdjustWebTextView
-                    && mZoomScale != 0) {
+                    && mZoomManager.isZoomAnimating()) {
                 // The WebTextView is up.  Keep track of this so we can adjust
                 // its size and placement when we finish zooming
                 mNeedToAdjustWebTextView = true;
@@ -3392,7 +3273,7 @@
                 }
             }
         } else {
-            canvas.scale(mActualScale, mActualScale);
+            canvas.scale(mZoomManager.mActualScale, mZoomManager.mActualScale);
         }
 
         boolean UIAnimationsRunning = false;
@@ -3405,7 +3286,7 @@
             invalidate();
         }
         mWebViewCore.drawContentPicture(canvas, color,
-                (animateZoom || mPreviewZoomOnly || UIAnimationsRunning),
+                (animateZoom || mZoomManager.mPreviewZoomOnly || UIAnimationsRunning),
                 animateScroll);
         if (mNativeClass == 0) return;
         // decide which adornments to draw
@@ -3418,10 +3299,10 @@
             }
         } else if (mShiftIsPressed
                 && !nativePageShouldHandleShiftAndArrows()) {
-            if (!animateZoom && !mPreviewZoomOnly) {
+            if (!animateZoom && !mZoomManager.mPreviewZoomOnly) {
                 extras = DRAW_EXTRAS_SELECTION;
                 nativeSetSelectionRegion(mTouchSelection || mExtendSelection);
-                nativeSetSelectionPointer(!mTouchSelection, mInvActualScale,
+                nativeSetSelectionPointer(!mTouchSelection, mZoomManager.mInvActualScale,
                         mSelectX, mSelectY - getTitleHeight(),
                         mExtendSelection);
             }
@@ -3535,13 +3416,11 @@
                 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
 
         // bring it back to the default scale so that user can enter text
-        boolean zoom = mActualScale < mDefaultScale;
+        boolean zoom = mZoomManager.mActualScale < mZoomManager.mDefaultScale;
         if (zoom) {
+            mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
             mZoomManager.mInZoomOverview = false;
-            mZoomCenterX = mLastTouchX;
-            mZoomCenterY = mLastTouchY;
-            // do not change text wrap scale so that there is no reflow
-            setNewZoomScale(mDefaultScale, false, false);
+            mZoomManager.setZoomScale(mZoomManager.mDefaultScale, false);
         }
         if (isTextView) {
             rebuildWebTextView();
@@ -4213,7 +4092,7 @@
             // system won't call onSizeChanged if the dimension is not changed.
             // In this case, we need to call sendViewSizeZoom() explicitly to
             // notify the WebKit about the new dimensions.
-            sendViewSizeZoom();
+            sendViewSizeZoom(false);
         }
         return changed;
     }
@@ -4231,8 +4110,7 @@
             if (mWebView.mWebViewCore != null) {
                 // we always force, in case our height changed, in which case we
                 // still want to send the notification over to webkit.
-                mWebView.setNewZoomScale(mWebView.mActualScale,
-                        mUpdateTextWrap, true);
+                mWebView.mZoomManager.refreshZoomScale(mUpdateTextWrap);
                 // update the zoom buttons as the scale can be changed
                 mWebView.mZoomManager.updateZoomPicker();
             }
@@ -4242,13 +4120,13 @@
     @Override
     protected void onSizeChanged(int w, int h, int ow, int oh) {
         super.onSizeChanged(w, h, ow, oh);
-        // Center zooming to the center of the screen.
-        if (mZoomScale == 0) { // unless we're already zooming
-            // To anchor at top left corner.
-            mZoomCenterX = 0;
-            mZoomCenterY = getVisibleTitleHeight();
-            mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-            mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+        // reset zoom and anchor to the top left corner of the screen
+        // unless we are already zooming
+        if (!mZoomManager.isZoomAnimating()) {
+            int visibleTitleHeight = getVisibleTitleHeight();
+            mZoomManager.setZoomCenter(0, visibleTitleHeight);
+            mAnchorX = viewToContentX(mScrollX);
+            mAnchorY = viewToContentY(visibleTitleHeight + mScrollY);
         }
 
         // adjust the max viewport width depending on the view dimensions. This
@@ -4267,7 +4145,7 @@
             // the new picture shows up.
             mZoomManager.mMinZoomScale = Math.min(1.0f, (float) getViewWidth()
                     / (mDrawHistory ? mHistoryPicture.getWidth()
-                            : mZoomOverviewWidth));
+                            : mZoomManager.mZoomOverviewWidth));
             if (mInitialScaleInPercent > 0) {
                 // limit the minZoomScale to the initialScale if it is set
                 float initialScale = mInitialScaleInPercent / 100.0f;
@@ -4296,7 +4174,7 @@
         // as getVisibleTitleHeight.
         int titleHeight = getTitleHeight();
         if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) {
-            sendViewSizeZoom();
+            sendViewSizeZoom(false);
         }
     }
 
@@ -4563,18 +4441,17 @@
         }
 
         public void onScaleEnd(ScaleGestureDetector detector) {
-            if (mPreviewZoomOnly) {
-                mPreviewZoomOnly = false;
-                mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-                mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+            if (mZoomManager.mPreviewZoomOnly) {
+                mZoomManager.mPreviewZoomOnly = false;
+                mAnchorX = viewToContentX((int) mZoomManager.mZoomCenterX + mScrollX);
+                mAnchorY = viewToContentY((int) mZoomManager.mZoomCenterY + mScrollY);
                 // don't reflow when zoom in; when zoom out, do reflow if the
                 // new scale is almost minimum scale;
-                boolean reflowNow = (mActualScale - mZoomManager.mMinZoomScale
-                        <= MINIMUM_SCALE_INCREMENT)
-                        || ((mActualScale <= 0.8 * mTextWrapScale));
+                boolean reflowNow = mZoomManager.isZoomedOut()
+                        || (mZoomManager.mActualScale <= 0.8 * mZoomManager.mTextWrapScale);
                 // force zoom after mPreviewZoomOnly is set to false so that the
                 // new view size will be passed to the WebKit
-                setNewZoomScale(mActualScale, reflowNow, true);
+                mZoomManager.refreshZoomScale(reflowNow);
                 // call invalidate() to draw without zoom filter
                 invalidate();
             }
@@ -4598,18 +4475,17 @@
 
         public boolean onScale(ScaleGestureDetector detector) {
             float scale = (float) (Math.round(detector.getScaleFactor()
-                    * mActualScale * 100) / 100.0);
-            if (Math.abs(scale - mActualScale) >= MINIMUM_SCALE_INCREMENT) {
-                mPreviewZoomOnly = true;
+                    * mZoomManager.mActualScale * 100) / 100.0);
+            if (mZoomManager.willScaleTriggerZoom(scale)) {
+                mZoomManager.mPreviewZoomOnly = true;
                 // limit the scale change per step
-                if (scale > mActualScale) {
-                    scale = Math.min(scale, mActualScale * 1.25f);
+                if (scale > mZoomManager.mActualScale) {
+                    scale = Math.min(scale, mZoomManager.mActualScale * 1.25f);
                 } else {
-                    scale = Math.max(scale, mActualScale * 0.8f);
+                    scale = Math.max(scale, mZoomManager.mActualScale * 0.8f);
                 }
-                mZoomCenterX = detector.getFocusX();
-                mZoomCenterY = detector.getFocusY();
-                setNewZoomScale(scale, false, false);
+                mZoomManager.setZoomCenter(detector.getFocusX(), detector.getFocusY());
+                mZoomManager.setZoomScale(scale, false);
                 invalidate();
                 return true;
             }
@@ -4741,7 +4617,7 @@
                                 contentX, contentY) : false;
                     }
                 } else { // the normal case
-                    mPreviewZoomOnly = false;
+                    mZoomManager.mPreviewZoomOnly = false;
                     mTouchMode = TOUCH_INIT_MODE;
                     mDeferTouchProcess = (!inFullScreenMode()
                             && mForwardTouchEvents) ? hitFocusedPlugin(
@@ -5487,6 +5363,19 @@
                 - getViewHeightWithTitle(), 0);
     }
 
+    boolean updateScrollCoordinates(int x, int y) {
+        int oldX = mScrollX;
+        int oldY = mScrollY;
+        mScrollX = x;
+        mScrollY = y;
+        if (oldX != mScrollX || oldY != mScrollY) {
+            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     public void flingScroll(int vx, int vy) {
         mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0,
                 computeMaxScrollY());
@@ -5560,32 +5449,6 @@
         invalidate();
     }
 
-    private boolean zoomWithPreview(float scale, boolean updateTextWrapScale) {
-        float oldScale = mActualScale;
-        mInitialScrollX = mScrollX;
-        mInitialScrollY = mScrollY;
-
-        // snap to DEFAULT_SCALE if it is close
-        if (Math.abs(scale - mDefaultScale) < MINIMUM_SCALE_INCREMENT) {
-            scale = mDefaultScale;
-        }
-
-        setNewZoomScale(scale, updateTextWrapScale, false);
-
-        if (oldScale != mActualScale) {
-            // use mZoomPickerScale to see zoom preview first
-            mZoomStart = SystemClock.uptimeMillis();
-            mInvInitialZoomScale = 1.0f / oldScale;
-            mInvFinalZoomScale = 1.0f / mActualScale;
-            mZoomScale = mActualScale;
-            WebViewCore.pauseUpdatePicture(mWebViewCore);
-            invalidate();
-            return true;
-        } else {
-            return false;
-        }
-    }
-
     /**
      * Returns a view containing zoom controls i.e. +/- buttons. The caller is
      * in charge of installing this view to the view hierarchy. This view will
@@ -5612,20 +5475,16 @@
         mZoomManager.dismissZoomPicker();
     }
 
+    float getDefaultZoomScale() {
+        return mZoomManager.mDefaultScale;
+    }
+
     /**
      * Perform zoom in in the webview
      * @return TRUE if zoom in succeeds. FALSE if no zoom changes.
      */
     public boolean zoomIn() {
-        // TODO: alternatively we can disallow this during draw history mode
-        switchOutDrawHistory();
-        mZoomManager.mInZoomOverview = false;
-        // Center zooming to the center of the screen.
-        mZoomCenterX = getViewWidth() * .5f;
-        mZoomCenterY = getViewHeight() * .5f;
-        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-        return zoomWithPreview(mActualScale * 1.25f, true);
+        return mZoomManager.zoomIn();
     }
 
     /**
@@ -5633,14 +5492,7 @@
      * @return TRUE if zoom out succeeds. FALSE if no zoom changes.
      */
     public boolean zoomOut() {
-        // TODO: alternatively we can disallow this during draw history mode
-        switchOutDrawHistory();
-        // Center zooming to the center of the screen.
-        mZoomCenterX = getViewWidth() * .5f;
-        mZoomCenterY = getViewHeight() * .5f;
-        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
-        return zoomWithPreview(mActualScale * 0.8f, true);
+        return mZoomManager.zoomOut();
     }
 
     private void updateSelection() {
@@ -5779,7 +5631,7 @@
         } else if (scale > mZoomManager.mMaxZoomScale) {
             scale = mZoomManager.mMaxZoomScale;
         }
-        if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) {
+        if (!mZoomManager.willScaleTriggerZoom(scale)) {
             if (contentToViewX(view.x) >= mScrollX
                     && contentToViewX(view.x + view.width) <= mScrollX
                             + viewWidth
@@ -5808,12 +5660,13 @@
         } else if (scale > mZoomManager.mMaxZoomScale) {
             scale = mZoomManager.mMaxZoomScale;
         }
-        if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) {
+        if (!mZoomManager.willScaleTriggerZoom(scale)) {
             pinScrollTo(contentToViewX(docX + docWidth / 2) - viewWidth / 2,
                     contentToViewY(docY + docHeight / 2) - viewHeight / 2,
                     true, 0);
         } else {
-            float oldScreenX = docX * mActualScale - mScrollX;
+            float actualScale = mZoomManager.mActualScale;
+            float oldScreenX = docX * actualScale - mScrollX;
             float rectViewX = docX * scale;
             float rectViewWidth = docWidth * scale;
             float newMaxWidth = mContentWidth * scale;
@@ -5824,9 +5677,9 @@
             } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) {
                 newScreenX = viewWidth - (newMaxWidth - rectViewX);
             }
-            mZoomCenterX = (oldScreenX * scale - newScreenX * mActualScale)
-                    / (scale - mActualScale);
-            float oldScreenY = docY * mActualScale + getTitleHeight()
+            float zoomCenterX = (oldScreenX * scale - newScreenX * actualScale)
+                    / (scale - actualScale);
+            float oldScreenY = docY * actualScale + getTitleHeight()
                     - mScrollY;
             float rectViewY = docY * scale + getTitleHeight();
             float rectViewHeight = docHeight * scale;
@@ -5838,9 +5691,10 @@
             } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) {
                 newScreenY = viewHeight - (newMaxHeight - rectViewY);
             }
-            mZoomCenterY = (oldScreenY * scale - newScreenY * mActualScale)
-                    / (scale - mActualScale);
-            zoomWithPreview(scale, false);
+            float zoomCenterY = (oldScreenY * scale - newScreenY * actualScale)
+                    / (scale - actualScale);
+            mZoomManager.setZoomCenter(zoomCenterX, zoomCenterY);
+            mZoomManager.animateZoom(scale, false);
         }
     }
 
@@ -5853,10 +5707,9 @@
         if (mWebViewCore.getSettings().getUseWideViewPort() == false) {
             return;
         }
-        mZoomCenterX = mLastTouchX;
-        mZoomCenterY = mLastTouchY;
-        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
-        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+        mZoomManager.setZoomCenter(mLastTouchX, mLastTouchY);
+        mAnchorX = viewToContentX((int) mLastTouchX + mScrollX);
+        mAnchorY = viewToContentY((int) mLastTouchX + mScrollY);
         WebSettings settings = getSettings();
         settings.setDoubleTapToastCount(0);
         // remove the zoom control after double tap
@@ -5864,11 +5717,7 @@
         ViewManager.ChildView plugin = mViewManager.hitTest(mAnchorX, mAnchorY);
         if (plugin != null) {
             if (isPluginFitOnScreen(plugin)) {
-                mZoomManager.mInZoomOverview = true;
-                // Force the titlebar fully reveal in overview mode
-                if (mScrollY < getTitleHeight()) mScrollY = 0;
-                zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth,
-                        true);
+                mZoomManager.zoomToOverview();
             } else {
                 mZoomManager.mInZoomOverview = false;
                 centerFitRect(plugin.x, plugin.y, plugin.width, plugin.height);
@@ -5877,28 +5726,24 @@
         }
         boolean zoomToDefault = false;
         if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS)
-                && (Math.abs(mActualScale - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT)) {
-            setNewZoomScale(mActualScale, true, true);
-            float overviewScale = (float) getViewWidth() / mZoomOverviewWidth;
-            if (Math.abs(mActualScale - overviewScale) < MINIMUM_SCALE_INCREMENT) {
+                && mZoomManager.willScaleTriggerZoom(mZoomManager.mTextWrapScale)) {
+            mZoomManager.refreshZoomScale(true);
+            float overviewScale = (float) getViewWidth() / mZoomManager.mZoomOverviewWidth;
+            if (!mZoomManager.willScaleTriggerZoom(overviewScale)) {
                 mZoomManager.mInZoomOverview = true;
             }
         } else if (!mZoomManager.mInZoomOverview) {
-            float newScale = (float) getViewWidth() / mZoomOverviewWidth;
-            if (Math.abs(mActualScale - newScale) >= MINIMUM_SCALE_INCREMENT) {
-                mZoomManager.mInZoomOverview = true;
-                // Force the titlebar fully reveal in overview mode
-                if (mScrollY < getTitleHeight()) mScrollY = 0;
-                zoomWithPreview(newScale, true);
-            } else if (Math.abs(mActualScale - mDefaultScale) >= MINIMUM_SCALE_INCREMENT) {
+            float newScale = (float) getViewWidth() / mZoomManager.mZoomOverviewWidth;
+            if (mZoomManager.willScaleTriggerZoom(newScale)) {
+                mZoomManager.zoomToOverview();
+            } else if (mZoomManager.willScaleTriggerZoom(mZoomManager.mDefaultScale)) {
                 zoomToDefault = true;
             }
         } else {
             zoomToDefault = true;
         }
         if (zoomToDefault) {
-            mZoomManager.mInZoomOverview = false;
-            int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
+            int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mZoomManager.mActualScale);
             if (left != NO_LEFTEDGE) {
                 // add a 5pt padding to the left edge.
                 int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5))
@@ -5906,14 +5751,14 @@
                 // Re-calculate the zoom center so that the new scroll x will be
                 // on the left edge.
                 if (viewLeft > 0) {
-                    mZoomCenterX = viewLeft * mDefaultScale
-                            / (mDefaultScale - mActualScale);
+                    mZoomManager.mZoomCenterX = viewLeft * mZoomManager.mDefaultScale
+                            / (mZoomManager.mDefaultScale - mZoomManager.mActualScale);
                 } else {
                     scrollBy(viewLeft, 0);
-                    mZoomCenterX = 0;
+                    mZoomManager.mZoomCenterX = 0;
                 }
             }
-            zoomWithPreview(mDefaultScale, true);
+            mZoomManager.zoomToDefaultLevel(true);
         }
     }
 
@@ -6015,6 +5860,12 @@
     public boolean requestChildRectangleOnScreen(View child,
                                                  Rect rect,
                                                  boolean immediate) {
+        // don't scroll while in zoom animation. When it is done, we will adjust
+        // the necessary components (e.g., WebTextView if it is in editing mode)
+        if(mZoomManager.isZoomAnimating()) {
+            return false;
+        }
+
         rect.offset(child.getLeft() - child.getScrollX(),
                 child.getTop() - child.getScrollY());
 
@@ -6248,13 +6099,13 @@
                             mZoomManager.mInZoomOverview = false;
 
                             if (mInitialScaleInPercent > 0) {
-                                setNewZoomScale(mInitialScaleInPercent / 100.0f,
-                                    mInitialScaleInPercent != mTextWrapScale * 100,
-                                    false);
+                                final float initialScale = mInitialScaleInPercent / 100.0f;
+                                final boolean reflowText =
+                                    mInitialScaleInPercent != mZoomManager.mTextWrapScale * 100;
+                                mZoomManager.setZoomScale(initialScale, reflowText);
                             } else if (restoreState.mViewScale > 0) {
-                                mTextWrapScale = restoreState.mTextWrapScale;
-                                setNewZoomScale(restoreState.mViewScale, false,
-                                    false);
+                                mZoomManager.mTextWrapScale = restoreState.mTextWrapScale;
+                                mZoomManager.setZoomScale(restoreState.mViewScale, false);
                             } else {
                                 mZoomManager.mInZoomOverview = useWideViewport
                                     && settings.getLoadWithOverviewMode();
@@ -6265,9 +6116,9 @@
                                 } else {
                                     scale = restoreState.mTextWrapScale;
                                 }
-                                setNewZoomScale(scale, Math.abs(scale
-                                    - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT,
-                                    false);
+                                mZoomManager.setZoomScale(scale,
+                                        ZoomManager.exceedsMinScaleIncrement(
+                                        mZoomManager.mTextWrapScale, scale));
                             }
                             setContentScrollTo(restoreState.mScrollX,
                                 restoreState.mScrollY);
@@ -6302,23 +6153,23 @@
                         // sMaxViewportWidth so that if the page doesn't behave
                         // well, the WebView won't go insane. limit the lower
                         // bound to match the default scale for mobile sites.
-                        mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math
-                                .max((int) (viewWidth / mDefaultScale), Math
-                                        .max(draw.mMinPrefWidth,
+                        mZoomManager.mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math
+                                .max((int) (viewWidth / mZoomManager.mDefaultScale),
+                                        Math.max(draw.mMinPrefWidth,
                                                 draw.mViewPoint.x)));
                     }
                     if (!mZoomManager.mMinZoomScaleFixed) {
-                        mZoomManager.mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
+                        mZoomManager.mMinZoomScale = (float) viewWidth / 
+                            mZoomManager.mZoomOverviewWidth;
                     }
                     if (!mDrawHistory && mZoomManager.mInZoomOverview) {
                         // fit the content width to the current view. Ignore
                         // the rounding error case.
-                        if (Math.abs((viewWidth * mInvActualScale)
-                                - mZoomOverviewWidth) > 1) {
-                            setNewZoomScale((float) viewWidth
-                                    / mZoomOverviewWidth, Math.abs(mActualScale
-                                    - mTextWrapScale) < MINIMUM_SCALE_INCREMENT,
-                                    false);
+                        if (Math.abs((viewWidth * mZoomManager.mInvActualScale)
+                                - mZoomManager.mZoomOverviewWidth) > 1) {
+                            mZoomManager.setZoomScale(
+                                    (float) viewWidth / mZoomManager.mZoomOverviewWidth,
+                                    !mZoomManager.willScaleTriggerZoom(mZoomManager.mTextWrapScale));
                         }
                     }
                     if (draw.mFocusSizeChanged && inEditingMode()) {
@@ -7075,7 +6926,7 @@
         // FIXME the divisor should be retrieved from somewhere
         // the closest thing today is hard-coded into ScrollView.java
         // (from ScrollView.java, line 363)   int maxJump = height/2;
-        return Math.round(height * mInvActualScale);
+        return Math.round(height * mZoomManager.mInvActualScale);
     }
 
     /**
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 974fcaaa..bb75cc7 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -2064,8 +2064,10 @@
         // adjust the default scale to match the densityDpi
         float adjust = 1.0f;
         if (mViewportDensityDpi == -1) {
-            if (WebView.DEFAULT_SCALE_PERCENT != 100) {
-                adjust = WebView.DEFAULT_SCALE_PERCENT / 100.0f;
+            // convert default zoom scale to a integer (percentage) to avoid any
+            // issues with floating point comparisons
+            if (mWebView != null && (int)(mWebView.getDefaultZoomScale() * 100) != 100) {
+                adjust = mWebView.getDefaultZoomScale();
             }
         } else if (mViewportDensityDpi > 0) {
             adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index 8ec771f..af521be 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import android.os.SystemClock;
 import android.view.View;
 
 class ZoomManager {
@@ -23,6 +24,7 @@
     static final String LOGTAG = "webviewZoom";
 
     private final WebView mWebView;
+    private final CallbackProxy mCallbackProxy;
 
     // manages the on-screen zoom functions of the WebView
     private ZoomControlEmbedded mEmbeddedZoomControl;
@@ -51,15 +53,219 @@
     // the last zoom scale.
     boolean mInZoomOverview = false;
 
-    public ZoomManager(WebView webView) {
+    // These keep track of the center point of the zoom.  They are used to
+    // determine the point around which we should zoom.
+    float mZoomCenterX;
+    float mZoomCenterY;
+
+    // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
+    // engadget always have wider mContentWidth no matter what viewport size is.
+    int mZoomOverviewWidth = WebView.DEFAULT_VIEWPORT_WIDTH;
+    float mTextWrapScale;
+
+    // the default zoom scale. This value will is initially set based on the
+    // display density, but can be changed at any time via the WebSettings.
+    float mDefaultScale;
+
+    private static float MINIMUM_SCALE_INCREMENT = 0.01f;
+
+    // set to true temporarily during ScaleGesture triggered zoom
+    boolean mPreviewZoomOnly = false;
+
+    // the current computed zoom scale and its inverse.
+    float mActualScale;
+    float mInvActualScale;
+    // if this is non-zero, it is used on drawing rather than mActualScale
+    float mZoomScale;
+    float mInvInitialZoomScale;
+    float mInvFinalZoomScale;
+    int mInitialScrollX;
+    int mInitialScrollY;
+    long mZoomStart;
+    static final int ZOOM_ANIMATION_LENGTH = 500;
+
+    public ZoomManager(WebView webView, CallbackProxy callbackProxy) {
         mWebView = webView;
+        mCallbackProxy = callbackProxy;
     }
 
     public void init(float density) {
-        DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
-        DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
+        setDefaultZoomScale(density);
         mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
         mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE;
+        mActualScale = density;
+        mInvActualScale = 1 / density;
+        mTextWrapScale = density;
+    }
+
+    public void updateDefaultZoomDensity(float density) {
+        if (Math.abs(density - mDefaultScale) > MINIMUM_SCALE_INCREMENT) {
+            float scaleFactor = density / mDefaultScale;
+            // set the new default density
+            setDefaultZoomScale(density);
+            // adjust the limits
+            mMaxZoomScale *= scaleFactor;
+            mMinZoomScale *= scaleFactor;
+            setZoomScale(mActualScale * scaleFactor, true);
+        }
+    }
+
+    private void setDefaultZoomScale(float defaultScale) {
+        mDefaultScale = defaultScale;
+        DEFAULT_MAX_ZOOM_SCALE = 4.0f * defaultScale;
+        DEFAULT_MIN_ZOOM_SCALE = 0.25f * defaultScale;
+    }
+
+    public void setZoomCenter(float x, float y) {
+        mZoomCenterX = x;
+        mZoomCenterY = y;
+    }
+
+    public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) {
+        return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT;
+    }
+
+    public boolean willScaleTriggerZoom(float scale) {
+        return exceedsMinScaleIncrement(scale, mActualScale);
+    }
+
+    public boolean isZoomedOut() {
+        return mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT;
+    }
+
+    public boolean isZoomAnimating() {
+        return mZoomScale != 0;
+    }
+
+    public boolean zoomIn() {
+        mInZoomOverview = false;
+        return zoom(1.25f);
+    }
+
+    public boolean zoomOut() {
+        return zoom(0.8f);
+    }
+
+    // returns TRUE if zoom out succeeds and FALSE if no zoom changes.
+    private boolean zoom(float zoomMultiplier) {
+        // TODO: alternatively we can disallow this during draw history mode
+        mWebView.switchOutDrawHistory();
+        // Center zooming to the center of the screen.
+        mZoomCenterX = mWebView.getViewWidth() * .5f;
+        mZoomCenterY = mWebView.getViewHeight() * .5f;
+        int anchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
+        int anchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
+        mWebView.setViewSizeAnchor(anchorX, anchorY);
+        return animateZoom(mActualScale * zoomMultiplier, true);
+    }
+
+    public void zoomToOverview() {
+        mInZoomOverview = true;
+        // Force the titlebar fully reveal in overview mode
+        int scrollY = mWebView.getScrollY();
+        if (scrollY < mWebView.getTitleHeight()) {
+            mWebView.updateScrollCoordinates(mWebView.getScrollX(), 0);
+        }
+        animateZoom((float) mWebView.getViewWidth() / mZoomOverviewWidth, true);
+    }
+
+    public void zoomToDefaultLevel(boolean reflowText) {
+        mInZoomOverview = false;
+        animateZoom(mDefaultScale, reflowText);
+    }
+
+    public boolean animateZoom(float scale, boolean reflowText) {
+        float oldScale = mActualScale;
+        mInitialScrollX = mWebView.getScrollX();
+        mInitialScrollY = mWebView.getScrollY();
+
+        // snap to DEFAULT_SCALE if it is close
+        if (!exceedsMinScaleIncrement(scale, mDefaultScale)) {
+            scale = mDefaultScale;
+        }
+
+        setZoomScale(scale, reflowText);
+
+        if (oldScale != mActualScale) {
+            // use mZoomPickerScale to see zoom preview first
+            mZoomStart = SystemClock.uptimeMillis();
+            mInvInitialZoomScale = 1.0f / oldScale;
+            mInvFinalZoomScale = 1.0f / mActualScale;
+            mZoomScale = mActualScale;
+            WebViewCore.pauseUpdatePicture(mWebView.getWebViewCore());
+            mWebView.invalidate();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void refreshZoomScale(boolean reflowText) {
+        setZoomScale(mActualScale, reflowText, true);
+    }
+
+    public void setZoomScale(float scale, boolean reflowText) {
+        setZoomScale(scale, reflowText, false);
+    }
+
+    private void setZoomScale(float scale, boolean reflowText, boolean force) {
+        if (scale < mMinZoomScale) {
+            scale = mMinZoomScale;
+            // set mInZoomOverview for non mobile sites
+            if (scale < mDefaultScale) {
+                mInZoomOverview = true;
+            }
+        } else if (scale > mMaxZoomScale) {
+            scale = mMaxZoomScale;
+        }
+
+        if (reflowText) {
+            mTextWrapScale = scale;
+        }
+
+        if (scale != mActualScale || force) {
+            float oldScale = mActualScale;
+            float oldInvScale = mInvActualScale;
+
+            if (scale != mActualScale && !mPreviewZoomOnly) {
+                mCallbackProxy.onScaleChanged(mActualScale, scale);
+            }
+
+            mActualScale = scale;
+            mInvActualScale = 1 / scale;
+
+            if (!mWebView.drawHistory()) {
+
+                // If history Picture is drawn, don't update scroll. They will
+                // be updated when we get out of that mode.
+                // update our scroll so we don't appear to jump
+                // i.e. keep the center of the doc in the center of the view
+                int oldX = mWebView.getScrollX();
+                int oldY = mWebView.getScrollY();
+                float ratio = scale * oldInvScale;
+                float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
+                float sy = ratio * oldY + (ratio - 1)
+                        * (mZoomCenterY - mWebView.getTitleHeight());
+
+                // Scale all the child views
+                mWebView.mViewManager.scaleAll();
+
+                // as we don't have animation for scaling, don't do animation
+                // for scrolling, as it causes weird intermediate state
+                int scrollX = mWebView.pinLocX(Math.round(sx));
+                int scrollY = mWebView.pinLocY(Math.round(sy));
+                if(!mWebView.updateScrollCoordinates(scrollX, scrollY)) {
+                    // the scroll position is adjusted at the beginning of the
+                    // zoom animation. But we want to update the WebKit at the
+                    // end of the zoom animation. See comments in onScaleEnd().
+                    mWebView.sendOurVisibleRect();
+                }
+            }
+
+            // if the we need to reflow the text then force the VIEW_SIZE_CHANGED
+            // event to be sent to WebKit
+            mWebView.sendViewSizeZoom(reflowText);
+        }
     }
 
     private ZoomControlBase getCurrentZoomControl() {
diff --git a/libs/audioflinger/AudioPolicyManagerBase.cpp b/libs/audioflinger/AudioPolicyManagerBase.cpp
index 381a958..549d661 100644
--- a/libs/audioflinger/AudioPolicyManagerBase.cpp
+++ b/libs/audioflinger/AudioPolicyManagerBase.cpp
@@ -902,7 +902,8 @@
 #ifdef AUDIO_POLICY_TEST
     Thread(false),
 #endif //AUDIO_POLICY_TEST
-    mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false)
+    mPhoneState(AudioSystem::MODE_NORMAL), mRingerMode(0), mMusicStopTime(0), mLimitRingtoneVolume(false),
+    mLastVoiceVolume(-1.0f)
 {
     mpClientInterface = clientInterface;
 
@@ -1713,29 +1714,38 @@
     }
 
     float volume = computeVolume(stream, index, output, device);
-    // do not set volume if the float value did not change
-    if (volume != mOutputs.valueFor(output)->mCurVolume[stream] || force) {
+    // We actually change the volume if:
+    // - the float value returned by computeVolume() changed
+    // - the force flag is set
+    if (volume != mOutputs.valueFor(output)->mCurVolume[stream] ||
+            force) {
         mOutputs.valueFor(output)->mCurVolume[stream] = volume;
         LOGV("setStreamVolume() for output %d stream %d, volume %f, delay %d", output, stream, volume, delayMs);
         if (stream == AudioSystem::VOICE_CALL ||
             stream == AudioSystem::DTMF ||
             stream == AudioSystem::BLUETOOTH_SCO) {
-            float voiceVolume = -1.0;
             // offset value to reflect actual hardware volume that never reaches 0
             // 1% corresponds roughly to first step in VOICE_CALL stream volume setting (see AudioService.java)
             volume = 0.01 + 0.99 * volume;
-            if (stream == AudioSystem::VOICE_CALL) {
-                voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
-            } else if (stream == AudioSystem::BLUETOOTH_SCO) {
-                voiceVolume = 1.0;
-            }
-            if (voiceVolume >= 0 && output == mHardwareOutput) {
-                mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
-            }
         }
         mpClientInterface->setStreamVolume((AudioSystem::stream_type)stream, volume, output, delayMs);
     }
 
+    if (stream == AudioSystem::VOICE_CALL ||
+        stream == AudioSystem::BLUETOOTH_SCO) {
+        float voiceVolume;
+        // Force voice volume to max for bluetooth SCO as volume is managed by the headset
+        if (stream == AudioSystem::VOICE_CALL) {
+            voiceVolume = (float)index/(float)mStreams[stream].mIndexMax;
+        } else {
+            voiceVolume = 1.0;
+        }
+        if (voiceVolume != mLastVoiceVolume && output == mHardwareOutput) {
+            mpClientInterface->setVoiceVolume(voiceVolume, delayMs);
+            mLastVoiceVolume = voiceVolume;
+        }
+    }
+
     return NO_ERROR;
 }
 
diff --git a/media/libeffects/EffectEqualizer.cpp b/media/libeffects/EffectEqualizer.cpp
index 3a5da4d..c08f4f5 100644
--- a/media/libeffects/EffectEqualizer.cpp
+++ b/media/libeffects/EffectEqualizer.cpp
@@ -20,6 +20,7 @@
 #include <cutils/log.h>
 #include <assert.h>
 #include <stdlib.h>
+#include <string.h>
 #include <new>
 #include "AudioEqualizer.h"
 #include "AudioBiquadFilter.h"
diff --git a/media/libeffects/EffectsFactory.c b/media/libeffects/EffectsFactory.c
index 07c4d80..35a10010 100644
--- a/media/libeffects/EffectsFactory.c
+++ b/media/libeffects/EffectsFactory.c
@@ -18,6 +18,8 @@
 //#define LOG_NDEBUG 0
 
 #include "EffectsFactory.h"
+#include <string.h>
+#include <stdlib.h>
 #include <dlfcn.h>
 
 
@@ -277,7 +279,7 @@
     if (ret < 0) {
         return ret;
     }
-    if (libPath == NULL || strnlen(libPath, PATH_MAX) >= PATH_MAX) {
+    if (libPath == NULL) {
         return -EINVAL;
     }
     return loadLibrary(libPath, handle);
diff --git a/media/libstagefright/OggExtractor.cpp b/media/libstagefright/OggExtractor.cpp
index d0d1b14..bd16db9 100644
--- a/media/libstagefright/OggExtractor.cpp
+++ b/media/libstagefright/OggExtractor.cpp
@@ -78,6 +78,8 @@
 
     void init();
 
+    sp<MetaData> getFileMetaData() { return mFileMeta; }
+
 private:
     struct Page {
         uint64_t mGranulePosition;
@@ -100,6 +102,7 @@
     vorbis_comment mVc;
 
     sp<MetaData> mMeta;
+    sp<MetaData> mFileMeta;
 
     ssize_t readPage(off_t offset, Page *page);
     status_t findNextPage(off_t startOffset, off_t *pageOffset);
@@ -107,6 +110,9 @@
     void verifyHeader(
             MediaBuffer *buffer, uint8_t type);
 
+    void parseFileMetaData();
+    void extractAlbumArt(const void *data, size_t size);
+
     MyVorbisExtractor(const MyVorbisExtractor &);
     MyVorbisExtractor &operator=(const MyVorbisExtractor &);
 };
@@ -188,9 +194,14 @@
       mNextLaceIndex(0),
       mFirstDataOffset(-1) {
     mCurrentPage.mNumSegments = 0;
+
+    vorbis_info_init(&mVi);
+    vorbis_comment_init(&mVc);
 }
 
 MyVorbisExtractor::~MyVorbisExtractor() {
+    vorbis_comment_clear(&mVc);
+    vorbis_info_clear(&mVi);
 }
 
 sp<MetaData> MyVorbisExtractor::getFormat() const {
@@ -305,7 +316,7 @@
         tmp.append(x);
     }
 
-    LOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string());
+    // LOGV("%c %s", page->mFlags & 1 ? '+' : ' ', tmp.string());
 
     return sizeof(header) + page->mNumSegments + totalSize;
 }
@@ -422,10 +433,6 @@
 }
 
 void MyVorbisExtractor::init() {
-    vorbis_info_init(&mVi);
-
-    vorbis_comment mVc;
-
     mMeta = new MetaData;
     mMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS);
 
@@ -509,6 +516,8 @@
         case 3:
         {
             CHECK_EQ(0, _vorbis_unpack_comment(&mVc, &bits));
+
+            parseFileMetaData();
             break;
         }
 
@@ -530,6 +539,192 @@
     return (mVi.bitrate_lower + mVi.bitrate_upper) / 2;
 }
 
+void MyVorbisExtractor::parseFileMetaData() {
+    mFileMeta = new MetaData;
+    mFileMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);
+
+    struct {
+        const char *const mTag;
+        uint32_t mKey;
+    } kMap[] = {
+        { "TITLE", kKeyTitle },
+        { "ARTIST", kKeyArtist },
+        { "ALBUM", kKeyAlbum },
+        { "COMPOSER", kKeyComposer },
+        { "GENRE", kKeyGenre },
+        { "AUTHOR", kKeyAuthor },
+        { "TRACKNUMBER", kKeyCDTrackNumber },
+        { "DISCNUMBER", kKeyDiscNumber },
+        { "DATE", kKeyDate },
+        { "LYRICIST", kKeyWriter },
+        { "METADATA_BLOCK_PICTURE", kKeyAlbumArt },
+    };
+
+    for (int i = 0; i < mVc.comments; ++i) {
+        const char *comment = mVc.user_comments[i];
+
+        for (size_t j = 0; j < sizeof(kMap) / sizeof(kMap[0]); ++j) {
+            size_t tagLen = strlen(kMap[j].mTag);
+            if (!strncasecmp(kMap[j].mTag, comment, tagLen)
+                    && comment[tagLen] == '=') {
+                if (kMap[j].mKey == kKeyAlbumArt) {
+                    extractAlbumArt(
+                            &comment[tagLen + 1],
+                            mVc.comment_lengths[i] - tagLen - 1);
+                } else {
+                    mFileMeta->setCString(kMap[j].mKey, &comment[tagLen + 1]);
+                }
+            }
+        }
+
+    }
+
+#if 0
+    for (int i = 0; i < mVc.comments; ++i) {
+        LOGI("comment #%d: '%s'", i + 1, mVc.user_comments[i]);
+    }
+#endif
+}
+
+// The returned buffer should be free()d.
+static uint8_t *DecodeBase64(const char *s, size_t size, size_t *outSize) {
+    *outSize = 0;
+
+    if ((size % 4) != 0) {
+        return NULL;
+    }
+
+    size_t n = size;
+    size_t padding = 0;
+    if (n >= 1 && s[n - 1] == '=') {
+        padding = 1;
+
+        if (n >= 2 && s[n - 2] == '=') {
+            padding = 2;
+        }
+    }
+
+    size_t outLen = 3 * size / 4 - padding;
+
+    *outSize = outLen;
+
+    void *buffer = malloc(outLen);
+
+    uint8_t *out = (uint8_t *)buffer;
+    size_t j = 0;
+    uint32_t accum = 0;
+    for (size_t i = 0; i < n; ++i) {
+        char c = s[i];
+        unsigned value;
+        if (c >= 'A' && c <= 'Z') {
+            value = c - 'A';
+        } else if (c >= 'a' && c <= 'z') {
+            value = 26 + c - 'a';
+        } else if (c >= '0' && c <= '9') {
+            value = 52 + c - '0';
+        } else if (c == '+') {
+            value = 62;
+        } else if (c == '/') {
+            value = 63;
+        } else if (c != '=') {
+            return NULL;
+        } else {
+            if (i < n - padding) {
+                return NULL;
+            }
+
+            value = 0;
+        }
+
+        accum = (accum << 6) | value;
+
+        if (((i + 1) % 4) == 0) {
+            out[j++] = (accum >> 16);
+
+            if (j < outLen) { out[j++] = (accum >> 8) & 0xff; }
+            if (j < outLen) { out[j++] = accum & 0xff; }
+
+            accum = 0;
+        }
+    }
+
+    return (uint8_t *)buffer;
+}
+
+void MyVorbisExtractor::extractAlbumArt(const void *data, size_t size) {
+    LOGV("extractAlbumArt from '%s'", (const char *)data);
+
+    size_t flacSize;
+    uint8_t *flac = DecodeBase64((const char *)data, size, &flacSize);
+
+    if (flac == NULL) {
+        LOGE("malformed base64 encoded data.");
+        return;
+    }
+
+    LOGV("got flac of size %d", flacSize);
+
+    uint32_t picType;
+    uint32_t typeLen;
+    uint32_t descLen;
+    uint32_t dataLen;
+    char type[128];
+
+    if (flacSize < 8) {
+        goto exit;
+    }
+
+    picType = U32_AT(flac);
+
+    if (picType != 3) {
+        // This is not a front cover.
+        goto exit;
+    }
+
+    typeLen = U32_AT(&flac[4]);
+    if (typeLen + 1 > sizeof(type)) {
+        goto exit;
+    }
+
+    if (flacSize < 8 + typeLen) {
+        goto exit;
+    }
+
+    memcpy(type, &flac[8], typeLen);
+    type[typeLen] = '\0';
+
+    LOGV("picType = %d, type = '%s'", picType, type);
+
+    if (!strcmp(type, "-->")) {
+        // This is not inline cover art, but an external url instead.
+        goto exit;
+    }
+
+    descLen = U32_AT(&flac[8 + typeLen]);
+
+    if (flacSize < 32 + typeLen + descLen) {
+        goto exit;
+    }
+
+    dataLen = U32_AT(&flac[8 + typeLen + 4 + descLen + 16]);
+
+    if (flacSize < 32 + typeLen + descLen + dataLen) {
+        goto exit;
+    }
+
+    LOGV("got image data, %d trailing bytes",
+         flacSize - 32 - typeLen - descLen - dataLen);
+
+    mFileMeta->setData(
+            kKeyAlbumArt, 0, &flac[8 + typeLen + 4 + descLen + 20], dataLen);
+
+    mFileMeta->setCString(kKeyAlbumArtMIME, type);
+
+exit:
+    free(flac);
+    flac = NULL;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 OggExtractor::OggExtractor(const sp<DataSource> &source)
@@ -570,15 +765,7 @@
 }
 
 sp<MetaData> OggExtractor::getMetaData() {
-    sp<MetaData> meta = new MetaData;
-
-    if (mInitCheck != OK) {
-        return meta;
-    }
-
-    meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_OGG);
-
-    return meta;
+    return mImpl->getFileMetaData();
 }
 
 bool SniffOgg(
diff --git a/media/libstagefright/StagefrightMediaScanner.cpp b/media/libstagefright/StagefrightMediaScanner.cpp
index ab17b04..2829638 100644
--- a/media/libstagefright/StagefrightMediaScanner.cpp
+++ b/media/libstagefright/StagefrightMediaScanner.cpp
@@ -26,10 +26,6 @@
 // Sonivox includes
 #include <libsonivox/eas.h>
 
-// Ogg Vorbis includes
-#include <Tremolo/ivorbiscodec.h>
-#include <Tremolo/ivorbisfile.h>
-
 namespace android {
 
 StagefrightMediaScanner::StagefrightMediaScanner()
@@ -103,48 +99,6 @@
     return OK;
 }
 
-static status_t HandleOGG(
-        const char *filename, MediaScannerClient *client) {
-    int duration;
-
-    FILE *file = fopen(filename,"r");
-    if (!file)
-        return UNKNOWN_ERROR;
-
-    OggVorbis_File vf;
-    if (ov_open(file, &vf, NULL, 0) < 0) {
-        return UNKNOWN_ERROR;
-    }
-
-    char **ptr=ov_comment(&vf,-1)->user_comments;
-    while(*ptr){
-        char *val = strstr(*ptr, "=");
-        if (val) {
-            int keylen = val++ - *ptr;
-            char key[keylen + 1];
-            strncpy(key, *ptr, keylen);
-            key[keylen] = 0;
-            if (!client->addStringTag(key, val)) goto failure;
-        }
-        ++ptr;
-    }
-
-    // Duration
-    duration = ov_time_total(&vf, -1);
-    if (duration > 0) {
-        char buffer[20];
-        sprintf(buffer, "%d", duration);
-        if (!client->addStringTag("duration", buffer)) goto failure;
-    }
-
-    ov_clear(&vf); // this also closes the FILE
-    return OK;
-
-failure:
-    ov_clear(&vf); // this also closes the FILE
-    return UNKNOWN_ERROR;
-}
-
 status_t StagefrightMediaScanner::processFile(
         const char *path, const char *mimeType,
         MediaScannerClient &client) {
@@ -176,10 +130,6 @@
         return HandleMIDI(path, &client);
     }
 
-    if (!strcasecmp(extension, ".ogg")) {
-        return HandleOGG(path, &client);
-    }
-
     if (mRetriever->setDataSource(path) == OK
             && mRetriever->setMode(
                 METADATA_MODE_METADATA_RETRIEVAL_ONLY) == OK) {
diff --git a/media/libstagefright/matroska/MatroskaExtractor.cpp b/media/libstagefright/matroska/MatroskaExtractor.cpp
index 197ccf8..339a7b5 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.cpp
+++ b/media/libstagefright/matroska/MatroskaExtractor.cpp
@@ -121,6 +121,29 @@
     }
 }
 
+struct BlockIterator {
+    BlockIterator(mkvparser::Segment *segment, unsigned long trackNum);
+
+    bool eos() const;
+
+    void advance();
+    void reset();
+    void seek(int64_t seekTimeUs);
+
+    const mkvparser::Block *block() const;
+    int64_t blockTimeUs() const;
+
+private:
+    mkvparser::Segment *mSegment;
+    unsigned long mTrackNum;
+
+    mkvparser::Cluster *mCluster;
+    const mkvparser::BlockEntry *mBlockEntry;
+
+    BlockIterator(const BlockIterator &);
+    BlockIterator &operator=(const BlockIterator &);
+};
+
 struct MatroskaSource : public MediaSource {
     MatroskaSource(
             const sp<MatroskaExtractor> &extractor, size_t index);
@@ -142,10 +165,8 @@
 
     sp<MatroskaExtractor> mExtractor;
     size_t mTrackIndex;
-    unsigned long mTrackNum;
     Type mType;
-    mkvparser::Cluster *mCluster;
-    const mkvparser::BlockEntry *mBlockEntry;
+    BlockIterator mBlockIter;
 
     status_t advance();
 
@@ -158,10 +179,8 @@
     : mExtractor(extractor),
       mTrackIndex(index),
       mType(OTHER),
-      mCluster(NULL),
-      mBlockEntry(NULL) {
-    mTrackNum = mExtractor->mTracks.itemAt(index).mTrackNum;
-
+      mBlockIter(mExtractor->mSegment,
+                 mExtractor->mTracks.itemAt(index).mTrackNum) {
     const char *mime;
     CHECK(mExtractor->mTracks.itemAt(index).mMeta->
             findCString(kKeyMIMEType, &mime));
@@ -174,8 +193,7 @@
 }
 
 status_t MatroskaSource::start(MetaData *params) {
-    mCluster = NULL;
-    mBlockEntry = NULL;
+    mBlockIter.reset();
 
     return OK;
 }
@@ -188,60 +206,95 @@
     return mExtractor->mTracks.itemAt(mTrackIndex).mMeta;
 }
 
-status_t MatroskaSource::advance() {
-    for (;;) {
-        if (mBlockEntry == NULL || mBlockEntry->EOS()) {
-            if (mCluster == NULL) {
-                mCluster = mExtractor->mSegment->GetFirst();
-            } else {
-                mCluster = mExtractor->mSegment->GetNext(mCluster);
+////////////////////////////////////////////////////////////////////////////////
+
+BlockIterator::BlockIterator(
+        mkvparser::Segment *segment, unsigned long trackNum)
+    : mSegment(segment),
+      mTrackNum(trackNum),
+      mCluster(NULL),
+      mBlockEntry(NULL) {
+    reset();
+}
+
+bool BlockIterator::eos() const {
+    return mCluster == NULL || mCluster->EOS();
+}
+
+void BlockIterator::advance() {
+    while (!eos()) {
+        if (mBlockEntry != NULL) {
+            mBlockEntry = mCluster->GetNext(mBlockEntry);
+        } else if (mCluster != NULL) {
+            mCluster = mSegment->GetNext(mCluster);
+
+            if (eos()) {
+                break;
             }
-            if (mCluster == NULL || mCluster->EOS()) {
-                return ERROR_END_OF_STREAM;
-            }
+
             mBlockEntry = mCluster->GetFirst();
         }
 
-        if (mBlockEntry->GetBlock()->GetTrackNumber() != mTrackNum) {
-            mBlockEntry = mCluster->GetNext(mBlockEntry);
-            continue;
+        if (mBlockEntry != NULL
+                && mBlockEntry->GetBlock()->GetTrackNumber() == mTrackNum) {
+            break;
         }
+    }
+}
 
-        break;
+void BlockIterator::reset() {
+    mCluster = mSegment->GetFirst();
+    mBlockEntry = mCluster->GetFirst();
+
+    while (!eos() && block()->GetTrackNumber() != mTrackNum) {
+        advance();
+    }
+}
+
+void BlockIterator::seek(int64_t seekTimeUs) {
+    mCluster = mSegment->GetCluster(seekTimeUs * 1000ll);
+    mBlockEntry = mCluster != NULL ? mCluster->GetFirst() : NULL;
+
+    while (!eos() && block()->GetTrackNumber() != mTrackNum) {
+        advance();
     }
 
-    return OK;
+    while (!eos() && !mBlockEntry->GetBlock()->IsKey()) {
+        advance();
+    }
 }
 
+const mkvparser::Block *BlockIterator::block() const {
+    CHECK(!eos());
+
+    return mBlockEntry->GetBlock();
+}
+
+int64_t BlockIterator::blockTimeUs() const {
+    return (mBlockEntry->GetBlock()->GetTime(mCluster) + 500ll) / 1000ll;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
 status_t MatroskaSource::read(
         MediaBuffer **out, const ReadOptions *options) {
     *out = NULL;
 
     int64_t seekTimeUs;
     if (options && options->getSeekTo(&seekTimeUs)) {
-        mBlockEntry = NULL;
-        mCluster = mExtractor->mSegment->GetCluster(seekTimeUs * 1000ll);
-
-        status_t err;
-        while ((err = advance()) == OK && !mBlockEntry->GetBlock()->IsKey()) {
-            mBlockEntry = mCluster->GetNext(mBlockEntry);
-        }
-
-        if (err != OK) {
-            return ERROR_END_OF_STREAM;
-        }
+        mBlockIter.seek(seekTimeUs);
     }
 
-    if (advance() != OK) {
+    if (mBlockIter.eos()) {
         return ERROR_END_OF_STREAM;
     }
 
-    const mkvparser::Block *block = mBlockEntry->GetBlock();
+    const mkvparser::Block *block = mBlockIter.block();
     size_t size = block->GetSize();
-    long long timeNs = block->GetTime(mCluster);
+    int64_t timeUs = mBlockIter.blockTimeUs();
 
     MediaBuffer *buffer = new MediaBuffer(size + 2);
-    buffer->meta_data()->setInt64(kKeyTime, (timeNs + 500) / 1000);
+    buffer->meta_data()->setInt64(kKeyTime, timeUs);
 
     long res = block->Read(
             mExtractor->mReader, (unsigned char *)buffer->data() + 2);
@@ -280,7 +333,7 @@
             buffer->range_length());
 #endif
 
-    mBlockEntry = mCluster->GetNext(mBlockEntry);
+    mBlockIter.advance();
 
     return OK;
 }
@@ -290,7 +343,8 @@
 MatroskaExtractor::MatroskaExtractor(const sp<DataSource> &source)
     : mDataSource(source),
       mReader(new DataSourceReader(mDataSource)),
-      mSegment(NULL) {
+      mSegment(NULL),
+      mExtractedThumbnails(false) {
     mkvparser::EBMLHeader ebmlHeader;
     long long pos;
     if (ebmlHeader.Parse(mReader, pos) < 0) {
@@ -342,6 +396,11 @@
         return NULL;
     }
 
+    if ((flags & kIncludeExtensiveMetaData) && !mExtractedThumbnails) {
+        findThumbnails();
+        mExtractedThumbnails = true;
+    }
+
     return mTracks.itemAt(index).mMeta;
 }
 
@@ -479,6 +538,37 @@
     }
 }
 
+void MatroskaExtractor::findThumbnails() {
+    for (size_t i = 0; i < mTracks.size(); ++i) {
+        TrackInfo *info = &mTracks.editItemAt(i);
+
+        const char *mime;
+        CHECK(info->mMeta->findCString(kKeyMIMEType, &mime));
+
+        if (strncasecmp(mime, "video/", 6)) {
+            continue;
+        }
+
+        BlockIterator iter(mSegment, info->mTrackNum);
+        int32_t i = 0;
+        int64_t thumbnailTimeUs = 0;
+        size_t maxBlockSize = 0;
+        while (!iter.eos() && i < 20) {
+            if (iter.block()->IsKey()) {
+                ++i;
+
+                size_t blockSize = iter.block()->GetSize();
+                if (blockSize > maxBlockSize) {
+                    maxBlockSize = blockSize;
+                    thumbnailTimeUs = iter.blockTimeUs();
+                }
+            }
+            iter.advance();
+        }
+        info->mMeta->setInt64(kKeyThumbnailTime, thumbnailTimeUs);
+    }
+}
+
 sp<MetaData> MatroskaExtractor::getMetaData() {
     sp<MetaData> meta = new MetaData;
     meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MATROSKA);
diff --git a/media/libstagefright/matroska/MatroskaExtractor.h b/media/libstagefright/matroska/MatroskaExtractor.h
index 7bf41a9..7471848 100644
--- a/media/libstagefright/matroska/MatroskaExtractor.h
+++ b/media/libstagefright/matroska/MatroskaExtractor.h
@@ -59,8 +59,10 @@
     sp<DataSource> mDataSource;
     DataSourceReader *mReader;
     mkvparser::Segment *mSegment;
+    bool mExtractedThumbnails;
 
     void addTracks();
+    void findThumbnails();
 
     MatroskaExtractor(const MatroskaExtractor &);
     MatroskaExtractor &operator=(const MatroskaExtractor &);
diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java
index 599023c..a497b72 100644
--- a/services/java/com/android/server/PackageManagerService.java
+++ b/services/java/com/android/server/PackageManagerService.java
@@ -9549,7 +9549,8 @@
     * Update media status on PackageManager.
     */
    public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
-       if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+       int callingUid = Binder.getCallingUid();
+       if (callingUid != 0 && callingUid != Process.SYSTEM_UID) {
            throw new SecurityException("Media status can only be updated by the system");
        }
        synchronized (mPackages) {
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 2328717..7a1587b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -239,9 +239,11 @@
     String getCdmaEriText();
 
     /**
-     * Returns true if CDMA provisioning needs to run.
+     * Returns true if OTA service provisioning needs to run.
+     * Only relevant on some technologies, others will always
+     * return false.
      */
-    boolean getCdmaNeedsProvisioning();
+    boolean needsOtaServiceProvisioning();
 
     /**
       * Returns the unread count of voicemails
diff --git a/telephony/java/com/android/internal/telephony/Phone.java b/telephony/java/com/android/internal/telephony/Phone.java
index 23325f6..8ff38d9 100644
--- a/telephony/java/com/android/internal/telephony/Phone.java
+++ b/telephony/java/com/android/internal/telephony/Phone.java
@@ -1533,6 +1533,11 @@
     boolean isOtaSpNumber(String dialStr);
 
     /**
+     * Returns true if OTA Service Provisioning needs to be performed.
+     */
+    boolean needsOtaServiceProvisioning();
+
+    /**
      * Register for notifications when CDMA call waiting comes
      *
      * @param h Handler that receives the notification message.
diff --git a/telephony/java/com/android/internal/telephony/PhoneBase.java b/telephony/java/com/android/internal/telephony/PhoneBase.java
index 74601e6..fbb658a 100644
--- a/telephony/java/com/android/internal/telephony/PhoneBase.java
+++ b/telephony/java/com/android/internal/telephony/PhoneBase.java
@@ -824,9 +824,19 @@
         logUnexpectedCdmaMethodCall("unregisterForSubscriptionInfoReady");
     }
 
+    /**
+     * Returns true if OTA Service Provisioning needs to be performed.
+     * If not overridden return false.
+     */
+    public boolean needsOtaServiceProvisioning() {
+        return false;
+    }
+
+    /**
+     * Return true if number is an OTASP number.
+     * If not overridden return false.
+     */
     public  boolean isOtaSpNumber(String dialStr) {
-        // This function should be overridden by the class CDMAPhone. Not implemented in GSMPhone.
-        logUnexpectedCdmaMethodCall("isOtaSpNumber");
         return false;
     }
 
diff --git a/telephony/java/com/android/internal/telephony/PhoneProxy.java b/telephony/java/com/android/internal/telephony/PhoneProxy.java
index e1511e6..8c2a661 100644
--- a/telephony/java/com/android/internal/telephony/PhoneProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneProxy.java
@@ -764,6 +764,10 @@
         mActivePhone.exitEmergencyCallbackMode();
     }
 
+    public boolean needsOtaServiceProvisioning(){
+        return mActivePhone.needsOtaServiceProvisioning();
+    }
+
     public boolean isOtaSpNumber(String dialStr){
         return mActivePhone.isOtaSpNumber(dialStr);
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
index 0c591e4..8934037 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CDMAPhone.java
@@ -867,26 +867,6 @@
         mRuimRecords.setVoiceMessageWaiting(1, mwi);
     }
 
-    /**
-     * Returns true if CDMA OTA Service Provisioning needs to be performed.
-     */
-    /* package */ boolean
-    needsOtaServiceProvisioning() {
-        String cdmaMin = getCdmaMin();
-        boolean needsProvisioning;
-        if (cdmaMin == null || (cdmaMin.length() < 6)) {
-            if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: illegal cdmaMin='"
-                                    + cdmaMin + "' assume provisioning needed.");
-            needsProvisioning = true;
-        } else {
-            needsProvisioning = (cdmaMin.equals(UNACTIVATED_MIN_VALUE)
-                    || cdmaMin.substring(0,6).equals(UNACTIVATED_MIN2_VALUE))
-                    || SystemProperties.getBoolean("test_cdma_setup", false);
-        }
-        if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: ret=" + needsProvisioning);
-        return needsProvisioning;
-    }
-
     @Override
     public void exitEmergencyCallbackMode() {
         if (mWakeLock.isHeld()) {
@@ -1180,6 +1160,26 @@
         mSMS.setCellBroadcastConfig(configValuesArray, response);
     }
 
+    /**
+     * Returns true if OTA Service Provisioning needs to be performed.
+     */
+    @Override
+    public boolean needsOtaServiceProvisioning() {
+        String cdmaMin = getCdmaMin();
+        boolean needsProvisioning;
+        if (cdmaMin == null || (cdmaMin.length() < 6)) {
+            if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: illegal cdmaMin='"
+                                    + cdmaMin + "' assume provisioning needed.");
+            needsProvisioning = true;
+        } else {
+            needsProvisioning = (cdmaMin.equals(UNACTIVATED_MIN_VALUE)
+                    || cdmaMin.substring(0,6).equals(UNACTIVATED_MIN2_VALUE))
+                    || SystemProperties.getBoolean("test_cdma_setup", false);
+        }
+        if (DBG) Log.d(LOG_TAG, "needsOtaServiceProvisioning: ret=" + needsProvisioning);
+        return needsProvisioning;
+    }
+
     private static final String IS683A_FEATURE_CODE = "*228";
     private static final int IS683A_FEATURE_CODE_NUM_DIGITS = 4;
     private static final int IS683A_SYS_SEL_CODE_NUM_DIGITS = 2;