Merge "Fix issue 2363154: Speech synthesis fails to start over A2DP after media server process crash."
diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java
index b207998..2a58677 100644
--- a/core/java/android/app/BackupAgent.java
+++ b/core/java/android/app/BackupAgent.java
@@ -21,6 +21,7 @@
 import android.backup.BackupDataOutput;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
@@ -116,7 +117,9 @@
         public void doBackup(ParcelFileDescriptor oldState,
                 ParcelFileDescriptor data,
                 ParcelFileDescriptor newState) throws RemoteException {
-            // !!! TODO - real implementation; for now just invoke the callbacks directly
+            // Ensure that we're running with the app's normal permission level
+            long token = Binder.clearCallingIdentity();
+
             if (DEBUG) Log.v(TAG, "doBackup() invoked");
             BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor());
             try {
@@ -127,12 +130,16 @@
             } catch (RuntimeException ex) {
                 Log.d(TAG, "onBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
                 throw ex;
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
         }
 
         public void doRestore(ParcelFileDescriptor data, int appVersionCode,
                 ParcelFileDescriptor newState) throws RemoteException {
-            // !!! TODO - real implementation; for now just invoke the callbacks directly
+            // Ensure that we're running with the app's normal permission level
+            long token = Binder.clearCallingIdentity();
+
             if (DEBUG) Log.v(TAG, "doRestore() invoked");
             BackupDataInput input = new BackupDataInput(data.getFileDescriptor());
             try {
@@ -143,6 +150,8 @@
             } catch (RuntimeException ex) {
                 Log.d(TAG, "onRestore (" + BackupAgent.this.getClass().getName() + ") threw", ex);
                 throw ex;
+            } finally {
+                Binder.restoreCallingIdentity(token);
             }
         }
     }
diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java
index 3296bbf..cbd8a26 100644
--- a/core/java/android/provider/Calendar.java
+++ b/core/java/android/provider/Calendar.java
@@ -62,7 +62,7 @@
     public static final String EVENT_BEGIN_TIME = "beginTime";
     public static final String EVENT_END_TIME = "endTime";
 
-    public static final String AUTHORITY = "calendar";
+    public static final String AUTHORITY = "com.android.calendar";
 
     /**
      * The content:// style URL for the top-level calendar authority
@@ -1413,4 +1413,25 @@
         // TODO: fill out this class when we actually start utilizing extendedproperties
         // in the calendar application.
    }
+
+    /**
+     * A table provided for sync adapters to use for storing private sync state data.
+     *
+     * @see SyncStateContract
+     */
+    public static final class SyncState implements SyncStateContract.Columns {
+        /**
+         * This utility class cannot be instantiated
+         */
+        private SyncState() {}
+
+        public static final String CONTENT_DIRECTORY =
+                SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.withAppendedPath(Calendar.CONTENT_URI, CONTENT_DIRECTORY);
+    }
 }
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index d5bb572..35f1ac6 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -412,6 +412,7 @@
      */
     public void setSupportZoom(boolean support) {
         mSupportZoom = support;
+        mWebView.updateMultiTouchSupport(mContext);
     }
 
     /**
@@ -426,6 +427,7 @@
      */
     public void setBuiltInZoomControls(boolean enabled) {
         mBuiltInZoomControls = enabled;
+        mWebView.updateMultiTouchSupport(mContext);
     }
     
     /**
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 2d9b52e..56650a6 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -21,6 +21,7 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.DialogInterface.OnCancelListener;
+import android.content.pm.PackageManager;
 import android.database.DataSetObserver;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -48,6 +49,7 @@
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
 import android.view.SoundEffectConstants;
 import android.view.VelocityTracker;
 import android.view.View;
@@ -362,6 +364,7 @@
     private static final int TOUCH_DOUBLE_TAP_MODE = 6;
     private static final int TOUCH_DONE_MODE = 7;
     private static final int TOUCH_SELECT_MODE = 8;
+    private static final int TOUCH_PINCH_DRAG = 9;
 
     // Whether to forward the touch events to WebCore
     private boolean mForwardTouchEvents = false;
@@ -460,6 +463,18 @@
     private static final int MOTIONLESS_TRUE            = 2;
     private int mHeldMotionless;
 
+    // whether support multi-touch
+    private static boolean mSupportMultiTouch;
+    // use the framework's ScaleGestureDetector to handle multi-touch
+    private ScaleGestureDetector mScaleDetector;
+    // minimum scale change during multi-touch zoom
+    private static float PREVIEW_SCALE_INCREMENT = 0.01f;
+
+    // the anchor point in the document space where VIEW_SIZE_CHANGED should
+    // apply to
+    private int mAnchorX;
+    private int mAnchorY;
+
     /**
      * Private message ids
      */
@@ -469,7 +484,7 @@
     private static final int SWITCH_TO_LONGPRESS        = 4;
     private static final int RELEASE_SINGLE_TAP         = 5;
     private static final int REQUEST_FORM_DATA          = 6;
-    private static final int RESUME_WEBCORE_UPDATE      = 7;
+    private static final int RESUME_WEBCORE_PRIORITY    = 7;
     private static final int DRAG_HELD_MOTIONLESS       = 8;
     private static final int AWAKEN_SCROLL_BARS         = 9;
 
@@ -488,7 +503,7 @@
     static final int MOVE_OUT_OF_PLUGIN                 = 19;
     static final int CLEAR_TEXT_ENTRY                   = 20;
     static final int UPDATE_TEXT_SELECTION_MSG_ID       = 21;
-
+    static final int SHOW_RECT_MSG_ID                   = 22;
     static final int LONG_PRESS_CENTER                  = 23;
     static final int PREVENT_TOUCH_ID                   = 24;
     static final int WEBCORE_NEED_TOUCH_EVENTS          = 25;
@@ -510,7 +525,7 @@
         "SWITCH_TO_LONGPRESS", //            = 4;
         "RELEASE_SINGLE_TAP", //             = 5;
         "REQUEST_FORM_DATA", //              = 6;
-        "RESUME_WEBCORE_UPDATE", //          = 7;
+        "RESUME_WEBCORE_PRIORITY", //        = 7;
         "DRAG_HELD_MOTIONLESS", //           = 8;
         "AWAKEN_SCROLL_BARS", //             = 9;
         "SCROLL_TO_MSG_ID", //               = 10;
@@ -525,7 +540,7 @@
         "MOVE_OUT_OF_PLUGIN", //             = 19;
         "CLEAR_TEXT_ENTRY", //               = 20;
         "UPDATE_TEXT_SELECTION_MSG_ID", //   = 21;
-        "22", //                             = 22;
+        "SHOW_RECT_MSG_ID", //               = 22;
         "LONG_PRESS_CENTER", //              = 23;
         "PREVENT_TOUCH_ID", //               = 24;
         "WEBCORE_NEED_TOUCH_EVENTS", //      = 25;
@@ -570,13 +585,13 @@
     // 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 mLastScale;
+    float mTextWrapScale;
 
     // default scale. Depending on the display density.
     static int DEFAULT_SCALE_PERCENT;
     private float mDefaultScale;
 
-    // set to true temporarily while the zoom control is being dragged
+    // set to true temporarily during ScaleGesture triggered zoom
     private boolean mPreviewZoomOnly = false;
 
     // computed scale and inverse, from mZoomWidth.
@@ -803,6 +818,20 @@
                     params;
             frameParams.gravity = Gravity.RIGHT;
         }
+        updateMultiTouchSupport(context);
+    }
+
+    void updateMultiTouchSupport(Context context) {
+        WebSettings settings = getSettings();
+        mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
+                && settings.supportZoom() && settings.getBuiltInZoomControls();
+        if (mSupportMultiTouch && (mScaleDetector == null)) {
+            mScaleDetector = new ScaleGestureDetector(context,
+                    new ScaleDetectorListener());
+        } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
+            mScaleDetector = null;
+        }
     }
 
     private void updateZoomButtonsEnabled() {
@@ -842,6 +871,7 @@
         mDefaultScale = density;
         mActualScale = density;
         mInvActualScale = 1 / density;
+        mTextWrapScale = density;
         DEFAULT_MAX_ZOOM_SCALE = 4.0f * density;
         DEFAULT_MIN_ZOOM_SCALE = 0.25f * density;
         mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE;
@@ -862,7 +892,7 @@
             mDefaultScale = density;
             mMaxZoomScale *= scaleFactor;
             mMinZoomScale *= scaleFactor;
-            setNewZoomScale(mActualScale * scaleFactor, false);
+            setNewZoomScale(mActualScale * scaleFactor, true, false);
         }
     }
 
@@ -1229,9 +1259,8 @@
             b.putInt("scrollX", mScrollX);
             b.putInt("scrollY", mScrollY);
             b.putFloat("scale", mActualScale);
-            if (mInZoomOverview) {
-                b.putFloat("lastScale", mLastScale);
-            }
+            b.putFloat("textwrapScale", mTextWrapScale);
+            b.putBoolean("overview", mInZoomOverview);
             return true;
         }
         return false;
@@ -1276,13 +1305,8 @@
                 // onSizeChanged() is called, the rest will be set
                 // correctly
                 mActualScale = scale;
-                float lastScale = b.getFloat("lastScale", -1.0f);
-                if (lastScale > 0) {
-                    mInZoomOverview = true;
-                    mLastScale = lastScale;
-                } else {
-                    mInZoomOverview = false;
-                }
+                mTextWrapScale = b.getFloat("textwrapScale", scale);
+                mInZoomOverview = b.getBoolean("overview");
                 invalidate();
                 return true;
             }
@@ -2012,12 +2036,18 @@
         contentSizeChanged(updateLayout);
     }
 
-    private void setNewZoomScale(float scale, boolean force) {
+    private void setNewZoomScale(float scale, boolean updateTextWrapScale,
+            boolean force) {
         if (scale < mMinZoomScale) {
             scale = mMinZoomScale;
         } else if (scale > mMaxZoomScale) {
             scale = 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
@@ -2027,9 +2057,7 @@
                 }
                 mActualScale = scale;
                 mInvActualScale = 1 / scale;
-                if (!mPreviewZoomOnly) {
-                    sendViewSizeZoom();
-                }
+                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
@@ -2057,10 +2085,9 @@
                 mScrollX = pinLocX(Math.round(sx));
                 mScrollY = pinLocY(Math.round(sy));
 
-                if (!mPreviewZoomOnly) {
-                    sendViewSizeZoom();
-                    sendOurVisibleRect();
-                }
+                // update webkit
+                sendViewSizeZoom();
+                sendOurVisibleRect();
             }
         }
     }
@@ -2070,6 +2097,8 @@
     private Rect mLastGlobalRect;
 
     private Rect sendOurVisibleRect() {
+        if (mPreviewZoomOnly) return mLastVisibleRectSent;
+
         Rect rect = new Rect();
         calcOurContentVisibleRect(rect);
         // Rect.equals() checks for null input.
@@ -2123,6 +2152,8 @@
         int mWidth;
         int mHeight;
         int mTextWrapWidth;
+        int mAnchorX;
+        int mAnchorY;
         float mScale;
         boolean mIgnoreHeight;
     }
@@ -2134,6 +2165,8 @@
      * @return true if new values were sent
      */
     private boolean sendViewSizeZoom() {
+        if (mPreviewZoomOnly) return false;
+
         int viewWidth = getViewWidth();
         int newWidth = Math.round(viewWidth * mInvActualScale);
         int newHeight = Math.round(getViewHeight() * mInvActualScale);
@@ -2153,16 +2186,15 @@
             ViewSizeData data = new ViewSizeData();
             data.mWidth = newWidth;
             data.mHeight = newHeight;
-            // while in zoom overview mode, the text are wrapped to the screen
-            // width matching mLastScale. So that we don't trigger re-flow while
-            // toggling between overview mode and normal mode.
-            data.mTextWrapWidth = mInZoomOverview ? Math.round(viewWidth
-                    / mLastScale) : newWidth;
+            data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);;
             data.mScale = mActualScale;
             data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure;
+            data.mAnchorX = mAnchorX;
+            data.mAnchorY = mAnchorY;
             mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data);
             mLastWidthSent = newWidth;
             mLastHeightSent = newHeight;
+            mAnchorX = mAnchorY = 0;
             return true;
         }
         return false;
@@ -3100,13 +3132,13 @@
             canvas.scale(mActualScale, mActualScale);
         }
 
-        mWebViewCore.drawContentPicture(canvas, color, animateZoom,
-                animateScroll);
+        mWebViewCore.drawContentPicture(canvas, color,
+                (animateZoom || mPreviewZoomOnly), animateScroll);
 
         drawLayers(canvas);
 
         if (mNativeClass == 0) return;
-        if (mShiftIsPressed && !animateZoom) {
+        if (mShiftIsPressed && !(animateZoom || mPreviewZoomOnly)) {
             if (mTouchSelection || mExtendSelection) {
                 nativeDrawSelectionRegion(canvas);
             }
@@ -3227,10 +3259,14 @@
             rebuildWebTextView();
             if (!inEditingMode()) return;
             imm.showSoftInput(mWebTextView, 0);
-            if (mInZoomOverview) {
-                // if in zoom overview mode, call doDoubleTap() to bring it back
-                // to normal mode so that user can enter text.
-                doDoubleTap();
+            // bring it back to the default scale so that user can enter text
+            if (mActualScale < mDefaultScale) {
+                mInZoomOverview = false;
+                mZoomCenterX = mLastTouchX;
+                mZoomCenterY = mLastTouchY;
+                // do not change text wrap scale so that there is no reflow
+                setNewZoomScale(mDefaultScale, false, false);
+                didUpdateTextViewBounds(true);
             }
         }
         else { // used by plugins
@@ -3872,6 +3908,25 @@
         return changed;
     }
 
+    private static class PostScale implements Runnable {
+        final WebView mWebView;
+        final boolean mUpdateTextWrap;
+
+        public PostScale(WebView webView, boolean updateTextWrap) {
+            mWebView = webView;
+            mUpdateTextWrap = updateTextWrap;
+        }
+
+        public void run() {
+            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);
+            }
+        }
+    }
+
     @Override
     protected void onSizeChanged(int w, int h, int ow, int oh) {
         super.onSizeChanged(w, h, ow, oh);
@@ -3879,6 +3934,8 @@
         if (mZoomScale == 0) { // unless we're already zooming
             mZoomCenterX = getViewWidth() * .5f;
             mZoomCenterY = getViewHeight() * .5f;
+            mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+            mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
         }
 
         // adjust the max viewport width depending on the view dimensions. This
@@ -3911,15 +3968,9 @@
         // requestLayout() is blocked during layout. As setNewZoomScale() will
         // call its child View to reposition itself through ViewManager's
         // scaleAll(), we need to post a Runnable to ensure requestLayout().
-        post(new Runnable() {
-            public void run() {
-                // we always force, in case our height changed, in which case we
-                // still want to send the notification over to webkit
-                if (mWebViewCore != null) {
-                    setNewZoomScale(mActualScale, true);
-                }
-            }
-        });
+        // <b/>
+        // only update the text wrap scale if width changed.
+        post(new PostScale(this, w != ow));
     }
 
     @Override
@@ -4127,6 +4178,76 @@
     private DragTracker mDragTracker;
     private DragTrackerHandler mDragTrackerHandler;
 
+    private class ScaleDetectorListener implements
+            ScaleGestureDetector.OnScaleGestureListener {
+
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            // cancel the single touch handling
+            cancelTouch();
+            if (mZoomButtonsController.isVisible()) {
+                mZoomButtonsController.setVisible(false);
+            }
+            // reset the zoom overview mode so that the page won't auto grow
+            mInZoomOverview = false;
+            // If it is in password mode, turn it off so it does not draw
+            // misplaced.
+            if (inEditingMode() && nativeFocusCandidateIsPassword()) {
+                mWebTextView.setInPassword(false);
+            }
+            return true;
+        }
+
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            if (mPreviewZoomOnly) {
+                mPreviewZoomOnly = false;
+                mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+                mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+                // don't reflow when zoom in; when zoom out, do reflow if the
+                // new scale is almost minimum scale;
+                boolean reflowNow = (mActualScale - mMinZoomScale <= 0.01f)
+                        || ((mActualScale <= 0.8 * 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);
+                // call invalidate() to draw without zoom filter
+                invalidate();
+            }
+            // adjust the edit text view if needed
+            if (inEditingMode() && didUpdateTextViewBounds(false)
+                    && nativeFocusCandidateIsPassword()) {
+                // If it is a password field, start drawing the
+                // WebTextView once again.
+                mWebTextView.setInPassword(true);
+            }
+            // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it
+            // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it
+            // may trigger the unwanted fling.
+            mTouchMode = TOUCH_PINCH_DRAG;
+            startTouch(detector.getFocusX(), detector.getFocusY(),
+                    mLastTouchTime);
+        }
+
+        public boolean onScale(ScaleGestureDetector detector) {
+            float scale = (float) (Math.round(detector.getScaleFactor()
+                    * mActualScale * 100) / 100.0);
+            if (Math.abs(scale - mActualScale) >= PREVIEW_SCALE_INCREMENT) {
+                mPreviewZoomOnly = true;
+                // limit the scale change per step
+                if (scale > mActualScale) {
+                    scale = Math.min(scale, mActualScale * 1.25f);
+                } else {
+                    scale = Math.max(scale, mActualScale * 0.8f);
+                }
+                mZoomCenterX = detector.getFocusX();
+                mZoomCenterY = detector.getFocusY();
+                setNewZoomScale(scale, false, false);
+                invalidate();
+                return true;
+            }
+            return false;
+        }
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
@@ -4138,11 +4259,45 @@
                     + mTouchMode);
         }
 
-        int action = ev.getAction();
-        float x = ev.getX();
-        float y = ev.getY();
+        int action;
+        float x, y;
         long eventTime = ev.getEventTime();
 
+        // FIXME: we may consider to give WebKit an option to handle multi-touch
+        // events later.
+        if (mSupportMultiTouch && ev.getPointerCount() > 1) {
+            if (mMinZoomScale < mMaxZoomScale) {
+                mScaleDetector.onTouchEvent(ev);
+                if (mScaleDetector.isInProgress()) {
+                    mLastTouchTime = eventTime;
+                    return true;
+                }
+                x = mScaleDetector.getFocusX();
+                y = mScaleDetector.getFocusY();
+                action = ev.getAction() & MotionEvent.ACTION_MASK;
+                if (action == MotionEvent.ACTION_POINTER_DOWN) {
+                    cancelTouch();
+                    action = MotionEvent.ACTION_DOWN;
+                } else if (action == MotionEvent.ACTION_POINTER_UP) {
+                    // set mLastTouchX/Y to the remaining point
+                    mLastTouchX = x;
+                    mLastTouchY = y;
+                } else if (action == MotionEvent.ACTION_MOVE) {
+                    // negative x or y indicate it is on the edge, skip it.
+                    if (x < 0 || y < 0) {
+                        return true;
+                    }
+                }
+            } else {
+                // if the page disallow zoom, skip multi-pointer action
+                return true;
+            }
+        } else {
+            action = ev.getAction();
+            x = ev.getX();
+            y = ev.getY();
+        }
+
         // Due to the touch screen edge effect, a touch closer to the edge
         // always snapped to the edge. As getViewWidth() can be different from
         // getWidth() due to the scrollbar, adjusting the point to match
@@ -4179,7 +4334,7 @@
                     // fling's velocity
                     mScroller.abortAnimation();
                     mTouchMode = TOUCH_DRAG_START_MODE;
-                    mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+                    mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
                 } else if (mShiftIsPressed) {
                     mSelectX = mScrollX + (int) x;
                     mSelectY = mScrollY + (int) y;
@@ -4201,6 +4356,7 @@
                         // continue, mTouchMode should be still TOUCH_INIT_MODE
                     }
                 } else {
+                    mPreviewZoomOnly = false;
                     mTouchMode = TOUCH_INIT_MODE;
                     mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES
                             : PREVENT_DRAG_NO;
@@ -4219,16 +4375,7 @@
                     mPrivateHandler.sendMessageDelayed(mPrivateHandler
                             .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
                 }
-                // Remember where the motion event started
-                mLastTouchX = x;
-                mLastTouchY = y;
-                mLastTouchTime = eventTime;
-                mVelocityTracker = VelocityTracker.obtain();
-                mSnapScrollMode = SNAP_NONE;
-                if (mDragTracker != null) {
-                    mDragTrackerHandler = new DragTrackerHandler(x, y,
-                                                                 mDragTracker);
-                }
+                startTouch(x, y, eventTime);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -4291,7 +4438,7 @@
                     deltaX = 0;
                     deltaY = 0;
 
-                    WebViewCore.pauseUpdate(mWebViewCore);
+                    WebViewCore.reducePriority(mWebViewCore);
                     if (!mDragFromTextInput) {
                         nativeHideCursor();
                     }
@@ -4454,7 +4601,7 @@
                                     || computeVerticalScrollExtent() < computeVerticalScrollRange())) {
                                 // we will not rewrite drag code here, but we
                                 // will try fling if it applies.
-                                WebViewCore.pauseUpdate(mWebViewCore);
+                                WebViewCore.reducePriority(mWebViewCore);
                                 // fall through to TOUCH_DRAG_MODE
                             } else {
                                 break;
@@ -4494,7 +4641,7 @@
                             break;
                         }
                         mLastVelocity = 0;
-                        WebViewCore.resumeUpdate(mWebViewCore);
+                        WebViewCore.resumePriority(mWebViewCore);
                         break;
                     case TOUCH_DRAG_START_MODE:
                     case TOUCH_DONE_MODE:
@@ -4511,33 +4658,49 @@
                 break;
             }
             case MotionEvent.ACTION_CANCEL: {
-                if (mDragTrackerHandler != null) {
-                    mDragTrackerHandler.stopDrag();
-                    mDragTrackerHandler = null;
-                }
-                // we also use mVelocityTracker == null to tell us that we are
-                // not "moving around", so we can take the slower/prettier
-                // mode in the drawing code
-                if (mVelocityTracker != null) {
-                    mVelocityTracker.recycle();
-                    mVelocityTracker = null;
-                }
-                if (mTouchMode == TOUCH_DRAG_MODE) {
-                    WebViewCore.resumeUpdate(mWebViewCore);
-                }
-                mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
-                mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
-                mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
-                mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
-                mHeldMotionless = MOTIONLESS_TRUE;
-                mTouchMode = TOUCH_DONE_MODE;
-                nativeHideCursor();
+                cancelTouch();
                 break;
             }
         }
         return true;
     }
 
+    private void startTouch(float x, float y, long eventTime) {
+        // Remember where the motion event started
+        mLastTouchX = x;
+        mLastTouchY = y;
+        mLastTouchTime = eventTime;
+        mVelocityTracker = VelocityTracker.obtain();
+        mSnapScrollMode = SNAP_NONE;
+        if (mDragTracker != null) {
+            mDragTrackerHandler = new DragTrackerHandler(x, y, mDragTracker);
+        }
+    }
+
+    private void cancelTouch() {
+        if (mDragTrackerHandler != null) {
+            mDragTrackerHandler.stopDrag();
+            mDragTrackerHandler = null;
+        }
+        // we also use mVelocityTracker == null to tell us that we are
+        // not "moving around", so we can take the slower/prettier
+        // mode in the drawing code
+        if (mVelocityTracker != null) {
+            mVelocityTracker.recycle();
+            mVelocityTracker = null;
+        }
+        if (mTouchMode == TOUCH_DRAG_MODE) {
+            WebViewCore.resumePriority(mWebViewCore);
+        }
+        mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS);
+        mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
+        mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS);
+        mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
+        mHeldMotionless = MOTIONLESS_TRUE;
+        mTouchMode = TOUCH_DONE_MODE;
+        nativeHideCursor();
+    }
+
     private long mTrackballFirstTime = 0;
     private long mTrackballLastTime = 0;
     private float mTrackballRemainsX = 0.0f;
@@ -4853,7 +5016,7 @@
             vy = vy * 3 / 4;
         }
         if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) {
-            WebViewCore.resumeUpdate(mWebViewCore);
+            WebViewCore.resumePriority(mWebViewCore);
             return;
         }
         float currentVelocity = mScroller.getCurrVelocity();
@@ -4887,7 +5050,7 @@
         // want to calculate how long the animation is going to run to precisely
         // resume the webcore update.
         final int time = mScroller.getDuration();
-        mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time);
+        mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time);
         awakenScrollBars(time);
         invalidate();
     }
@@ -4902,7 +5065,7 @@
             scale = mDefaultScale;
         }
 
-        setNewZoomScale(scale, false);
+        setNewZoomScale(scale, true, false);
 
         if (oldScale != mActualScale) {
             // use mZoomPickerScale to see zoom preview first
@@ -4910,9 +5073,6 @@
             mInvInitialZoomScale = 1.0f / oldScale;
             mInvFinalZoomScale = 1.0f / mActualScale;
             mZoomScale = mActualScale;
-            if (!mInZoomOverview) {
-                mLastScale = scale;
-            }
             invalidate();
             return true;
         } else {
@@ -5010,18 +5170,13 @@
     public boolean zoomIn() {
         // TODO: alternatively we can disallow this during draw history mode
         switchOutDrawHistory();
+        mInZoomOverview = false;
         // Center zooming to the center of the screen.
-        if (mInZoomOverview) {
-            // if in overview mode, bring it back to normal mode
-            mLastTouchX = getViewWidth() * .5f;
-            mLastTouchY = getViewHeight() * .5f;
-            doDoubleTap();
-            return true;
-        } else {
-            mZoomCenterX = getViewWidth() * .5f;
-            mZoomCenterY = getViewHeight() * .5f;
-            return zoomWithPreview(mActualScale * 1.25f);
-        }
+        mZoomCenterX = getViewWidth() * .5f;
+        mZoomCenterY = getViewHeight() * .5f;
+        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+        return zoomWithPreview(mActualScale * 1.25f);
     }
 
     /**
@@ -5031,20 +5186,12 @@
     public boolean zoomOut() {
         // TODO: alternatively we can disallow this during draw history mode
         switchOutDrawHistory();
-        float scale = mActualScale * 0.8f;
-        if (scale < (mMinZoomScale + 0.1f)
-                && mWebViewCore.getSettings().getUseWideViewPort()
-                && mZoomOverviewWidth > Math.ceil(getViewWidth()
-                        * mInvActualScale)) {
-            // when zoom out to min scale, switch to overview mode
-            doDoubleTap();
-            return true;
-        } else {
-            // Center zooming to the center of the screen.
-            mZoomCenterX = getViewWidth() * .5f;
-            mZoomCenterY = getViewHeight() * .5f;
-            return zoomWithPreview(scale);
-        }
+        // 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);
     }
 
     private void updateSelection() {
@@ -5088,7 +5235,7 @@
         mLastTouchTime = eventTime;
         if (!mScroller.isFinished()) {
             abortAnimation();
-            mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE);
+            mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
         }
         mSnapScrollMode = SNAP_NONE;
         mVelocityTracker = VelocityTracker.obtain();
@@ -5168,15 +5315,21 @@
         }
     }
 
+    // Rule for double tap:
+    // 1. if the current scale is not same as the text wrap scale and layout
+    //    algorithm is NARROW_COLUMNS, fit to column;
+    // 2. if the current state is not overview mode, change to overview mode;
+    // 3. if the current state is overview mode, change to default scale.
     private void doDoubleTap() {
         if (mWebViewCore.getSettings().getUseWideViewPort() == false) {
             return;
         }
         mZoomCenterX = mLastTouchX;
         mZoomCenterY = mLastTouchY;
-        mInZoomOverview = !mInZoomOverview;
-        // remove the zoom control after double tap
+        mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+        mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
         WebSettings settings = getSettings();
+        // remove the zoom control after double tap
         if (settings.getBuiltInZoomControls()) {
             if (mZoomButtonsController.isVisible()) {
                 mZoomButtonsController.setVisible(false);
@@ -5190,28 +5343,45 @@
             }
         }
         settings.setDoubleTapToastCount(0);
-        if (mInZoomOverview) {
+        boolean zoomToDefault = false;
+        if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS)
+                && (Math.abs(mActualScale - mTextWrapScale) >= 0.01f)) {
+            setNewZoomScale(mActualScale, true, true);
+            float overviewScale = (float) getViewWidth() / mZoomOverviewWidth;
+            if (Math.abs(mActualScale - overviewScale) < 0.01f) {
+                mInZoomOverview = true;
+            }
+        } else if (!mInZoomOverview) {
             float newScale = (float) getViewWidth() / mZoomOverviewWidth;
-            if (Math.abs(mActualScale - newScale) < 0.01f) {
-                // reset mInZoomOverview to false if scale doesn't change
-                mInZoomOverview = false;
-            } else {
+            if (Math.abs(mActualScale - newScale) >= 0.01f) {
+                mInZoomOverview = true;
                 // Force the titlebar fully reveal in overview mode
                 if (mScrollY < getTitleHeight()) mScrollY = 0;
                 zoomWithPreview(newScale);
+            } else if (Math.abs(mActualScale - mDefaultScale) >= 0.01f) {
+                zoomToDefault = true;
             }
         } else {
-            // mLastTouchX and mLastTouchY are the point in the current viewport
-            int contentX = viewToContentX((int) mLastTouchX + mScrollX);
-            int contentY = viewToContentY((int) mLastTouchY + mScrollY);
-            int left = nativeGetBlockLeftEdge(contentX, contentY, mActualScale);
+            zoomToDefault = true;
+        }
+        if (zoomToDefault) {
+            mInZoomOverview = false;
+            int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
             if (left != NO_LEFTEDGE) {
-                // add a 5pt padding to the left edge. Re-calculate the zoom
-                // center so that the new scroll x will be on the left edge.
-                mZoomCenterX = left < 5 ? 0 : (left - 5) * mLastScale
-                        * mActualScale / (mLastScale - mActualScale);
+                // add a 5pt padding to the left edge.
+                int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5))
+                        - mScrollX;
+                // 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);
+                } else {
+                    scrollBy(viewLeft, 0);
+                    mZoomCenterX = 0;
+                }
             }
-            zoomWithPreview(mLastScale);
+            zoomWithPreview(mDefaultScale);
         }
     }
 
@@ -5503,9 +5673,6 @@
                     boolean hasRestoreState = restoreState != null;
                     if (hasRestoreState) {
                         mInZoomOverview = false;
-                        mLastScale = mInitialScaleInPercent > 0
-                                ? mInitialScaleInPercent / 100.0f
-                                        : restoreState.mTextWrapScale;
                         if (restoreState.mMinScale == 0) {
                             if (restoreState.mMobileSite) {
                                 if (draw.mMinPrefWidth >
@@ -5513,6 +5680,8 @@
                                     mMinZoomScale = (float) viewWidth
                                             / draw.mMinPrefWidth;
                                     mMinZoomScaleFixed = false;
+                                    mInZoomOverview = useWideViewport &&
+                                            settings.getLoadWithOverviewMode();
                                 } else {
                                     mMinZoomScale = restoreState.mDefaultScale;
                                     mMinZoomScaleFixed = true;
@@ -5530,17 +5699,29 @@
                         } else {
                             mMaxZoomScale = restoreState.mMaxScale;
                         }
-                        setNewZoomScale(mLastScale, false);
+                        if (mInitialScaleInPercent > 0) {
+                            setNewZoomScale(mInitialScaleInPercent / 100.0f,
+                                    mInitialScaleInPercent != mTextWrapScale * 100,
+                                    false);
+                        } else if (restoreState.mViewScale > 0) {
+                            mTextWrapScale = restoreState.mTextWrapScale;
+                            setNewZoomScale(restoreState.mViewScale, false,
+                                    false);
+                        } else {
+                            mInZoomOverview = useWideViewport
+                                    && settings.getLoadWithOverviewMode();
+                            float scale;
+                            if (mInZoomOverview) {
+                                scale = (float) viewWidth
+                                        / DEFAULT_VIEWPORT_WIDTH;
+                            } else {
+                                scale = restoreState.mTextWrapScale;
+                            }
+                            setNewZoomScale(scale, Math.abs(scale
+                                    - mTextWrapScale) >= 0.01f, false);
+                        }
                         setContentScrollTo(restoreState.mScrollX,
                                 restoreState.mScrollY);
-                        if (useWideViewport
-                                && settings.getLoadWithOverviewMode()) {
-                            if (restoreState.mViewScale == 0
-                                    || (restoreState.mMobileSite
-                                    && mMinZoomScale < restoreState.mDefaultScale)) {
-                                mInZoomOverview = true;
-                            }
-                        }
                         // As we are on a new page, remove the WebTextView. This
                         // is necessary for page loads driven by webkit, and in
                         // particular when the user was on a password field, so
@@ -5570,11 +5751,14 @@
                         mPictureListener.onNewPicture(WebView.this, capturePicture());
                     }
                     if (useWideViewport) {
-                        // limit mZoomOverviewWidth to sMaxViewportWidth so that
-                        // if the page doesn't behave well, the WebView won't go
-                        // insane.
+                        // limit mZoomOverviewWidth upper bound to
+                        // 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(draw.mMinPrefWidth, draw.mViewPoint.x));
+                                .max((int) (viewWidth / mDefaultScale), Math
+                                        .max(draw.mMinPrefWidth,
+                                                draw.mViewPoint.x)));
                     }
                     if (!mMinZoomScaleFixed) {
                         mMinZoomScale = (float) viewWidth / mZoomOverviewWidth;
@@ -5585,7 +5769,8 @@
                         if (Math.abs((viewWidth * mInvActualScale)
                                 - mZoomOverviewWidth) > 1) {
                             setNewZoomScale((float) viewWidth
-                                    / mZoomOverviewWidth, false);
+                                    / mZoomOverviewWidth, Math.abs(mActualScale
+                                            - mTextWrapScale) < 0.01f, false);
                         }
                     }
                     if (draw.mFocusSizeChanged && inEditingMode()) {
@@ -5710,8 +5895,8 @@
                         mWebTextView.setAdapterCustom(adapter);
                     }
                     break;
-                case RESUME_WEBCORE_UPDATE:
-                    WebViewCore.resumeUpdate(mWebViewCore);
+                case RESUME_WEBCORE_PRIORITY:
+                    WebViewCore.resumePriority(mWebViewCore);
                     break;
 
                 case LONG_PRESS_CENTER:
@@ -5789,7 +5974,7 @@
                     doMotionUp(msg.arg1, msg.arg2);
                     break;
 
-                case SHOW_FULLSCREEN:
+                case SHOW_FULLSCREEN: {
                     WebViewCore.PluginFullScreenData data
                             = (WebViewCore.PluginFullScreenData) msg.obj;
                     if (data.mNpp != 0 && data.mView != null) {
@@ -5837,15 +6022,19 @@
                     if (width > viewWidth || height > viewHeight) {
                         mZoomCenterX = viewWidth * .5f;
                         mZoomCenterY = viewHeight * .5f;
+                        // do not change text wrap scale so that there is no
+                        // reflow
                         setNewZoomScale(mActualScale
                                 / Math.max((float) width / viewWidth,
-                                        (float) height / viewHeight), false);
+                                        (float) height / viewHeight), false,
+                                false);
                     }
                     // Now update the bound
                     mFullScreenHolder.updateBound(contentToViewX(data.mDocX)
                             - mScrollX, contentToViewY(data.mDocY) - mScrollY,
                             contentToViewDimension(data.mDocWidth),
                             contentToViewDimension(data.mDocHeight));
+                    }
                     break;
 
                 case HIDE_FULLSCREEN:
@@ -5862,6 +6051,44 @@
                     }
                     break;
 
+                case SHOW_RECT_MSG_ID: {
+                    WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj;
+                    int x = mScrollX;
+                    int left = contentToViewDimension(data.mLeft);
+                    int width = contentToViewDimension(data.mWidth);
+                    int maxWidth = contentToViewDimension(data.mContentWidth);
+                    int viewWidth = getViewWidth();
+                    if (width < viewWidth) {
+                        // center align
+                        x += left + width / 2 - mScrollX - viewWidth / 2;
+                    } else {
+                        x += (int) (left + data.mXPercentInDoc * width
+                                - mScrollX - data.mXPercentInView * viewWidth);
+                    }
+                    // use the passing content width to cap x as the current
+                    // mContentWidth may not be updated yet
+                    x = Math.max(0,
+                            (Math.min(maxWidth, x + viewWidth)) - viewWidth);
+                    int y = mScrollY;
+                    int top = contentToViewDimension(data.mTop);
+                    int height = contentToViewDimension(data.mHeight);
+                    int maxHeight = contentToViewDimension(data.mContentHeight);
+                    int viewHeight = getViewHeight();
+                    if (height < viewHeight) {
+                        // middle align
+                        y += top + height / 2 - mScrollY - viewHeight / 2;
+                    } else {
+                        y += (int) (top + data.mYPercentInDoc * height
+                                - mScrollY - data.mYPercentInView * viewHeight);
+                    }
+                    // use the passing content height to cap y as the current
+                    // mContentHeight may not be updated yet
+                    y = Math.max(0,
+                            (Math.min(maxHeight, y + viewHeight) - viewHeight));
+                    scrollTo(x, y);
+                    }
+                    break;
+
                 default:
                     super.handleMessage(msg);
                     break;
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index d509bb4..6700d71 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -482,8 +482,8 @@
         should this be called nativeSetViewPortSize?
     */
     private native void nativeSetSize(int width, int height, int screenWidth,
-            float scale, int realScreenWidth, int screenHeight,
-            boolean ignoreHeight);
+            float scale, int realScreenWidth, int screenHeight, int anchorX,
+            int anchorY, boolean ignoreHeight);
 
     private native int nativeGetContentMinPrefWidth();
 
@@ -1033,6 +1033,7 @@
                                     (WebView.ViewSizeData) msg.obj;
                             viewSizeChanged(data.mWidth, data.mHeight,
                                     data.mTextWrapWidth, data.mScale,
+                                    data.mAnchorX, data.mAnchorY,
                                     data.mIgnoreHeight);
                             break;
                         }
@@ -1584,7 +1585,7 @@
 
     // notify webkit that our virtual view size changed size (after inv-zoom)
     private void viewSizeChanged(int w, int h, int textwrapWidth, float scale,
-            boolean ignoreHeight) {
+            int anchorX, int anchorY, boolean ignoreHeight) {
         if (DebugFlags.WEB_VIEW_CORE) {
             Log.v(LOGTAG, "viewSizeChanged w=" + w + "; h=" + h
                     + "; textwrapWidth=" + textwrapWidth + "; scale=" + scale);
@@ -1616,12 +1617,14 @@
                             Math.max(WebView.DEFAULT_VIEWPORT_WIDTH,
                                     nativeGetContentMinPrefWidth())));
                 }
-            } else {
+            } else if (mViewportWidth > 0) {
                 width = Math.max(w, mViewportWidth);
+            } else {
+                width = textwrapWidth;
             }
         }
         nativeSetSize(width, width == w ? h : Math.round((float) width * h / w),
-                textwrapWidth, scale, w, h, ignoreHeight);
+                textwrapWidth, scale, w, h, anchorX, anchorY, ignoreHeight);
         // Remember the current width and height
         boolean needInvalidate = (mCurrentViewWidth == 0);
         mCurrentViewWidth = w;
@@ -1778,7 +1781,7 @@
         return result;
     }
 
-    static void pauseUpdate(WebViewCore core) {
+    static void reducePriority(WebViewCore core) {
         // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
         sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
         sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
@@ -1786,7 +1789,7 @@
                 .obtainMessage(WebCoreThread.REDUCE_PRIORITY));
     }
 
-    static void resumeUpdate(WebViewCore core) {
+    static void resumePriority(WebViewCore core) {
         // remove the pending REDUCE_PRIORITY and RESUME_PRIORITY messages
         sWebCoreHandler.removeMessages(WebCoreThread.REDUCE_PRIORITY);
         sWebCoreHandler.removeMessages(WebCoreThread.RESUME_PRIORITY);
@@ -2070,14 +2073,12 @@
         mRestoreState.mScrollY = mRestoredY;
         mRestoreState.mMobileSite = (0 == mViewportWidth);
         if (mRestoredScale > 0) {
+            mRestoreState.mViewScale = mRestoredScale / 100.0f;
             if (mRestoredScreenWidthScale > 0) {
                 mRestoreState.mTextWrapScale =
                         mRestoredScreenWidthScale / 100.0f;
-                // 0 will trigger WebView to turn on zoom overview mode
-                mRestoreState.mViewScale = 0;
             } else {
-                mRestoreState.mViewScale = mRestoreState.mTextWrapScale =
-                        mRestoredScale / 100.0f;
+                mRestoreState.mTextWrapScale = mRestoreState.mViewScale;
             }
         } else {
             if (mViewportInitialScale > 0) {
@@ -2110,6 +2111,7 @@
             data.mTextWrapWidth = data.mWidth;
             data.mScale = -1.0f;
             data.mIgnoreHeight = false;
+            data.mAnchorX = data.mAnchorY = 0;
             // send VIEW_SIZE_CHANGED to the front of the queue so that we can
             // avoid pushing the wrong picture to the WebView side. If there is
             // a VIEW_SIZE_CHANGED in the queue, probably from WebView side,
@@ -2145,6 +2147,7 @@
                 data.mTextWrapWidth = Math.round(webViewWidth
                         / mRestoreState.mTextWrapScale);
                 data.mIgnoreHeight = false;
+                data.mAnchorX = data.mAnchorY = 0;
                 // send VIEW_SIZE_CHANGED to the front of the queue so that we
                 // can avoid pushing the wrong picture to the WebView side.
                 mEventHub.removeMessages(EventHub.VIEW_SIZE_CHANGED);
@@ -2364,6 +2367,40 @@
         childView.removeView();
     }
 
+    // called by JNI
+    static class ShowRectData {
+        int mLeft;
+        int mTop;
+        int mWidth;
+        int mHeight;
+        int mContentWidth;
+        int mContentHeight;
+        float mXPercentInDoc;
+        float mXPercentInView;
+        float mYPercentInDoc;
+        float mYPercentInView;
+    }
+
+    private void showRect(int left, int top, int width, int height,
+            int contentWidth, int contentHeight, float xPercentInDoc,
+            float xPercentInView, float yPercentInDoc, float yPercentInView) {
+        if (mWebView != null) {
+            ShowRectData data = new ShowRectData();
+            data.mLeft = left;
+            data.mTop = top;
+            data.mWidth = width;
+            data.mHeight = height;
+            data.mContentWidth = contentWidth;
+            data.mContentHeight = contentHeight;
+            data.mXPercentInDoc = xPercentInDoc;
+            data.mXPercentInView = xPercentInView;
+            data.mYPercentInDoc = yPercentInDoc;
+            data.mYPercentInView = yPercentInView;
+            Message.obtain(mWebView.mPrivateHandler, WebView.SHOW_RECT_MSG_ID,
+                    data).sendToTarget();
+        }
+    }
+
     private native void nativePause();
     private native void nativeResume();
     private native void nativeFreeMemory();
diff --git a/include/media/IOMX.h b/include/media/IOMX.h
index bb7677df..2f61cbe 100644
--- a/include/media/IOMX.h
+++ b/include/media/IOMX.h
@@ -166,6 +166,7 @@
             OMX_U32 flags;
             OMX_TICKS timestamp;
             OMX_PTR platform_private;
+            OMX_PTR data_ptr;
         } extended_buffer_data;
 
     } u;
diff --git a/include/media/stagefright/OMXCodec.h b/include/media/stagefright/OMXCodec.h
index ac2f662..82dd2b5 100644
--- a/include/media/stagefright/OMXCodec.h
+++ b/include/media/stagefright/OMXCodec.h
@@ -94,6 +94,7 @@
         kRequiresFlushCompleteEmulation      = 16,
         kRequiresAllocateBufferOnOutputPorts = 32,
         kRequiresFlushBeforeShutdown         = 64,
+        kDefersOutputBufferAllocation        = 128,
     };
 
     struct BufferInfo {
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 2686489..986dcb2 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -301,8 +301,8 @@
         quirks |= kRequiresAllocateBufferOnOutputPorts;
     }
     if (!strncmp(componentName, "OMX.qcom.video.decoder.", 23)) {
-        // XXX Required on P....on only.
         quirks |= kRequiresAllocateBufferOnOutputPorts;
+        quirks |= kDefersOutputBufferAllocation;
     }
 
     if (!strncmp(componentName, "OMX.TI.", 7)) {
@@ -1237,8 +1237,15 @@
         info.mMediaBuffer = NULL;
 
         if (portIndex == kPortIndexOutput) {
-            info.mMediaBuffer = new MediaBuffer(info.mData, info.mSize);
-            info.mMediaBuffer->setObserver(this);
+            if (!(mOMXLivesLocally
+                        && (mQuirks & kRequiresAllocateBufferOnOutputPorts)
+                        && (mQuirks & kDefersOutputBufferAllocation))) {
+                // If the node does not fill in the buffer ptr at this time,
+                // we will defer creating the MediaBuffer until receiving
+                // the first FILL_BUFFER_DONE notification instead.
+                info.mMediaBuffer = new MediaBuffer(info.mData, info.mSize);
+                info.mMediaBuffer->setObserver(this);
+            }
         }
 
         mPortBuffers[portIndex].push(info);
@@ -1346,6 +1353,22 @@
             } else if (mPortStatus[kPortIndexOutput] != SHUTTING_DOWN) {
                 CHECK_EQ(mPortStatus[kPortIndexOutput], ENABLED);
 
+                if (info->mMediaBuffer == NULL) {
+                    CHECK(mOMXLivesLocally);
+                    CHECK(mQuirks & kRequiresAllocateBufferOnOutputPorts);
+                    CHECK(mQuirks & kDefersOutputBufferAllocation);
+
+                    // The qcom video decoders on Nexus don't actually allocate
+                    // output buffer memory on a call to OMX_AllocateBuffer
+                    // the "pBuffer" member of the OMX_BUFFERHEADERTYPE
+                    // structure is only filled in later.
+
+                    info->mMediaBuffer = new MediaBuffer(
+                            msg.u.extended_buffer_data.data_ptr,
+                            info->mSize);
+                    info->mMediaBuffer->setObserver(this);
+                }
+
                 MediaBuffer *buffer = info->mMediaBuffer;
 
                 buffer->set_range(
diff --git a/media/libstagefright/SampleIterator.cpp b/media/libstagefright/SampleIterator.cpp
index faad42ba..7155c61 100644
--- a/media/libstagefright/SampleIterator.cpp
+++ b/media/libstagefright/SampleIterator.cpp
@@ -54,6 +54,10 @@
 status_t SampleIterator::seekTo(uint32_t sampleIndex) {
     LOGV("seekTo(%d)", sampleIndex);
 
+    if (sampleIndex >= mTable->mNumSampleSizes) {
+        return ERROR_END_OF_STREAM;
+    }
+
     if (mTable->mSampleToChunkOffset < 0
             || mTable->mChunkOffsetOffset < 0
             || mTable->mSampleSizeOffset < 0
diff --git a/media/libstagefright/StagefrightMetadataRetriever.cpp b/media/libstagefright/StagefrightMetadataRetriever.cpp
index 313a9ed..dfba74f 100644
--- a/media/libstagefright/StagefrightMetadataRetriever.cpp
+++ b/media/libstagefright/StagefrightMetadataRetriever.cpp
@@ -276,8 +276,6 @@
 }
 
 const char *StagefrightMetadataRetriever::extractMetadata(int keyCode) {
-    LOGV("extractMetadata %d", keyCode);
-
     if (mExtractor == NULL) {
         return NULL;
     }
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
index 918d055c..9ca060d 100644
--- a/media/libstagefright/omx/OMX.cpp
+++ b/media/libstagefright/omx/OMX.cpp
@@ -365,6 +365,7 @@
     msg.u.extended_buffer_data.flags = pBuffer->nFlags;
     msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp;
     msg.u.extended_buffer_data.platform_private = pBuffer->pPlatformPrivate;
+    msg.u.extended_buffer_data.data_ptr = pBuffer->pBuffer;
 
     mDispatcher->post(msg);
 
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index c22c21b..d2f8ced 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -1111,10 +1111,10 @@
         if (cur_c == NULL) {
             // no current context
             if (draw != EGL_NO_SURFACE || read != EGL_NO_SURFACE) {
-                // calling eglMakeCurrent( ..., EGL_NO_CONTEXT, !=0, !=0);
-                return setError(EGL_BAD_PARAMETER, EGL_FALSE);
+                // calling eglMakeCurrent( ..., !=0, !=0, EGL_NO_CONTEXT);
+                return setError(EGL_BAD_MATCH, EGL_FALSE);
             }
-            // not an error, there is just not current context.
+            // not an error, there is just no current context.
             return EGL_TRUE;
         }
     }