Merge "Fix issue 2349345: Media sound output stuck on earpiece rather than speaker."
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/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java
index 987e763..6c5f912 100644
--- a/core/java/android/speech/RecognizerIntent.java
+++ b/core/java/android/speech/RecognizerIntent.java
@@ -154,4 +154,12 @@
      * {@link #ACTION_RECOGNIZE_SPEECH}. Only present when {@link Activity#RESULT_OK} is returned.
      */
     public static final String EXTRA_RESULTS = "android.speech.extra.RESULTS";
+    
+    /**
+     * Triggers the voice search settings activity.
+     * 
+     * @hide pending API council approval, to be unhidden for Froyo
+     */
+    public static final String ACTION_VOICE_SEARCH_SETTINGS =
+            "android.speech.action.VOICE_SEARCH_SETTINGS";
 }
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/include/ui/CameraParameters.h b/include/ui/CameraParameters.h
index 9e4e140..a5ea133 100644
--- a/include/ui/CameraParameters.h
+++ b/include/ui/CameraParameters.h
@@ -109,9 +109,10 @@
     // The height (in pixels) of EXIF thumbnail in Jpeg picture.
     // Example value: "384". Read/write.
     static const char KEY_JPEG_THUMBNAIL_HEIGHT[];
-    // Supported EXIF thumbnail sizes (width x height).
-    // Example value: "512x384,320x240". Read only.
-    static const char KEY_SUPPORTED_THUMBNAIL_SIZES[];
+    // Supported EXIF thumbnail sizes (width x height). 0x0 means not thumbnail
+    // in EXIF.
+    // Example value: "512x384,320x240,0x0". Read only.
+    static const char KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES[];
     // The quality of the EXIF thumbnail in Jpeg picture. The range is 1 to 100,
     // with 100 being the best.
     // Example value: "90". Read/write.
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index cf6d7fc..2c9bdaa 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -168,27 +168,27 @@
         }
         mShader.append(mUserShader);
     } else {
+        mShader.append("attribute vec4 ATTRIB_LegacyPosition;\n");
+        mShader.append("attribute vec4 ATTRIB_LegacyColor;\n");
+        mShader.append("attribute vec3 ATTRIB_LegacyNormal;\n");
+        mShader.append("attribute float ATTRIB_LegacyPointSize;\n");
+        mShader.append("attribute vec4 ATTRIB_LegacyTexture;\n");
+
         for (uint32_t ct=0; ct < mUniformCount; ct++) {
             mShader.append("uniform mat4 ");
             mShader.append(mUniformNames[ct]);
             mShader.append(";\n");
         }
 
-        for (uint32_t ct=VertexArray::POSITION; ct < mAttribCount; ct++) {
-            mShader.append("attribute vec4 ");
-            mShader.append(mAttribNames[ct]);
-            mShader.append(";\n");
-        }
-
         mShader.append("void main() {\n");
-        mShader.append("  gl_Position = UNI_MVP * ATTRIB_Position;\n");
-        mShader.append("  gl_PointSize = ATTRIB_PointSize.x;\n");
+        mShader.append("  gl_Position = UNI_MVP * ATTRIB_LegacyPosition;\n");
+        mShader.append("  gl_PointSize = ATTRIB_LegacyPointSize;\n");
 
-        mShader.append("  varColor = ATTRIB_Color;\n");
+        mShader.append("  varColor = ATTRIB_LegacyColor;\n");
         if (mTextureMatrixEnable) {
-            mShader.append("  varTex0 = UNI_TexMatrix * ATTRIB_Texture;\n");
+            mShader.append("  varTex0 = UNI_TexMatrix * ATTRIB_LegacyTexture;\n");
         } else {
-            mShader.append("  varTex0 = ATTRIB_Texture;\n");
+            mShader.append("  varTex0 = ATTRIB_LegacyTexture;\n");
         }
         //mShader.append("  pos.x = pos.x / 480.0;\n");
         //mShader.append("  pos.y = pos.y / 800.0;\n");
@@ -326,10 +326,11 @@
     }
 }
 
+
 void ProgramVertex::init(Context *rsc)
 {
+    mAttribCount = 0;
     if (mUserShader.size() > 0) {
-        mAttribCount = 0;
         for (uint32_t ct=0; ct < mInputCount; ct++) {
             initAddUserElement(mInputElements[ct].get(), mAttribNames, &mAttribCount, "ATTRIB_");
         }
@@ -340,13 +341,6 @@
             initAddUserElement(mConstantTypes[ct]->getElement(), mUniformNames, &mUniformCount, "UNI_");
         }
     } else {
-        mAttribCount = 5;
-        mAttribNames[0].setTo("ATTRIB_Position");
-        mAttribNames[1].setTo("ATTRIB_Color");
-        mAttribNames[2].setTo("ATTRIB_Normal");
-        mAttribNames[3].setTo("ATTRIB_PointSize");
-        mAttribNames[4].setTo("ATTRIB_Texture");
-
         mUniformCount = 2;
         mUniformNames[0].setTo("UNI_MVP");
         mUniformNames[1].setTo("UNI_TexMatrix");
diff --git a/libs/rs/rsScriptC_Lib.cpp b/libs/rs/rsScriptC_Lib.cpp
index eeb9468..6b8ed0d 100644
--- a/libs/rs/rsScriptC_Lib.cpp
+++ b/libs/rs/rsScriptC_Lib.cpp
@@ -687,7 +687,7 @@
 
     float vtx[] = { x1, y1, z1, x2, y2, z2 };
     VertexArray va;
-    va.setPosition(2, GL_FLOAT, 12, (uint32_t)&vtx);
+    va.addLegacy(GL_FLOAT, 3, 12, RS_KIND_POSITION, false, (uint32_t)vtx);
     if (rsc->checkVersion2_0()) {
         va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
     } else {
@@ -705,7 +705,7 @@
     float vtx[] = { x, y, z };
 
     VertexArray va;
-    va.setPosition(1, GL_FLOAT, 12, (uint32_t)&vtx);
+    va.addLegacy(GL_FLOAT, 3, 12, RS_KIND_POSITION, false, (uint32_t)vtx);
     if (rsc->checkVersion2_0()) {
         va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
     } else {
@@ -737,8 +737,8 @@
     const float tex[] = {u1,v1, u2,v2, u3,v3, u4,v4};
 
     VertexArray va;
-    va.setPosition(3, GL_FLOAT, 12, (uint32_t)&vtx);
-    va.setTexture(2, GL_FLOAT, 8, (uint32_t)&tex);
+    va.addLegacy(GL_FLOAT, 3, 12, RS_KIND_POSITION, false, (uint32_t)vtx);
+    va.addLegacy(GL_FLOAT, 2, 8, RS_KIND_TEXTURE, false, (uint32_t)tex);
     if (rsc->checkVersion2_0()) {
         va.setupGL2(rsc, &rsc->mStateVertexArray, &rsc->mShaderCache);
     } else {
diff --git a/libs/rs/rsShaderCache.cpp b/libs/rs/rsShaderCache.cpp
index 8ac2487..3a1f370 100644
--- a/libs/rs/rsShaderCache.cpp
+++ b/libs/rs/rsShaderCache.cpp
@@ -86,6 +86,7 @@
     e->vtx = vtx->getShaderID();
     e->frag = frag->getShaderID();
     e->program = glCreateProgram();
+    e->mUserVertexProgram = vtx->isUserProgram();
     if (mEntries[mEntryCount].program) {
         GLuint pgm = e->program;
         glAttachShader(pgm, vtx->getShaderID());
@@ -93,13 +94,16 @@
         glAttachShader(pgm, frag->getShaderID());
 
         if (!vtx->isUserProgram()) {
-            glBindAttribLocation(pgm, VertexArray::POSITION, "ATTRIB_Position");
-            glBindAttribLocation(pgm, VertexArray::COLOR, "ATTRIB_Color");
-            glBindAttribLocation(pgm, VertexArray::NORMAL, "ATTRIB_Normal");
-            glBindAttribLocation(pgm, VertexArray::POINT_SIZE, "ATTRIB_PointSize");
-            glBindAttribLocation(pgm, VertexArray::TEXTURE, "ATTRIB_T0");
-        } else {
-
+            glBindAttribLocation(pgm, 0, "ATTRIB_LegacyPosition");
+            glBindAttribLocation(pgm, 1, "ATTRIB_LegacyColor");
+            glBindAttribLocation(pgm, 2, "ATTRIB_LegacyNormal");
+            glBindAttribLocation(pgm, 3, "ATTRIB_LegacyPointSize");
+            glBindAttribLocation(pgm, 4, "ATTRIB_LegacyTexture");
+            e->mVtxAttribSlots[RS_KIND_POSITION] = 0;
+            e->mVtxAttribSlots[RS_KIND_COLOR] = 1;
+            e->mVtxAttribSlots[RS_KIND_NORMAL] = 2;
+            e->mVtxAttribSlots[RS_KIND_POINT_SIZE] = 3;
+            e->mVtxAttribSlots[RS_KIND_TEXTURE] = 4;
         }
 
         //LOGE("e2 %x", glGetError());
@@ -120,10 +124,12 @@
             }
             glDeleteProgram(pgm);
         }
-        for (uint32_t ct=0; ct < vtx->getAttribCount(); ct++) {
-            e->mVtxAttribSlots[ct] = glGetAttribLocation(pgm, vtx->getAttribName(ct));
-            if (rsc->props.mLogShaders) {
-                LOGV("vtx A %i, %s = %d\n", ct, vtx->getAttribName(ct).string(), e->mVtxAttribSlots[ct]);
+        if (vtx->isUserProgram()) {
+            for (uint32_t ct=0; ct < vtx->getAttribCount(); ct++) {
+                e->mVtxAttribSlots[ct] = glGetAttribLocation(pgm, vtx->getAttribName(ct));
+                if (rsc->props.mLogShaders) {
+                    LOGV("vtx A %i, %s = %d\n", ct, vtx->getAttribName(ct).string(), e->mVtxAttribSlots[ct]);
+                }
             }
         }
         for (uint32_t ct=0; ct < vtx->getUniformCount(); ct++) {
diff --git a/libs/rs/rsShaderCache.h b/libs/rs/rsShaderCache.h
index ede3734..7aa8183 100644
--- a/libs/rs/rsShaderCache.h
+++ b/libs/rs/rsShaderCache.h
@@ -44,6 +44,7 @@
     int32_t vtxUniformSlot(uint32_t a) const {return mCurrent->mVtxUniformSlots[a];}
     int32_t fragAttribSlot(uint32_t a) const {return mCurrent->mFragAttribSlots[a];}
     int32_t fragUniformSlot(uint32_t a) const {return mCurrent->mFragUniformSlots[a];}
+    bool isUserVertexProgram() const {return mCurrent->mUserVertexProgram;}
 
 protected:
     typedef struct {
@@ -54,6 +55,7 @@
         int32_t mVtxUniformSlots[Program::MAX_UNIFORMS];
         int32_t mFragAttribSlots[Program::MAX_ATTRIBS];
         int32_t mFragUniformSlots[Program::MAX_UNIFORMS];
+        bool mUserVertexProgram;
     } entry_t;
     entry_t *mEntries;
     entry_t *mCurrent;
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index ddadd9f..22a267a 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -207,36 +207,48 @@
 
     uint32_t stride = mElement->getSizeBytes();
     if (mGL.mVtx.size) {
-        va->setPosition(mGL.mVtx.size,
-                        mGL.mVtx.type,
-                        stride,
-                        mGL.mVtx.offset);
+        va->addLegacy(mGL.mVtx.type,
+                      mGL.mVtx.size,
+                      stride,
+                      RS_KIND_POSITION,
+                      false,
+                      mGL.mVtx.offset);
     }
 
     if (mGL.mNorm.size) {
-        va->setNormal(mGL.mNorm.type,
-                      stride,
-                      mGL.mNorm.offset);
+        va->addLegacy(mGL.mNorm.type,
+                     3,
+                     stride,
+                     RS_KIND_NORMAL,
+                     false,
+                     mGL.mNorm.offset);
     }
 
     if (mGL.mColor.size) {
-        va->setColor(mGL.mColor.size,
-                     mGL.mColor.type,
+        va->addLegacy(mGL.mColor.type,
+                     mGL.mColor.size,
                      stride,
+                     RS_KIND_COLOR,
+                     true,
                      mGL.mColor.offset);
     }
 
     if (mGL.mTex.size) {
-        va->setTexture(mGL.mTex.size,
-                       mGL.mTex.type,
-                       stride,
-                       mGL.mTex.offset);
+        va->addLegacy(mGL.mTex.type,
+                     mGL.mTex.size,
+                     stride,
+                     RS_KIND_TEXTURE,
+                     false,
+                     mGL.mTex.offset);
     }
 
     if (mGL.mPointSize.size) {
-        va->setPointSize(mGL.mPointSize.type,
-                         stride,
-                         mGL.mPointSize.offset);
+        va->addLegacy(mGL.mPointSize.type,
+                     1,
+                     stride,
+                     RS_KIND_POINT_SIZE,
+                     false,
+                     mGL.mPointSize.offset);
     }
 
 }
@@ -249,7 +261,7 @@
     uint32_t stride = mElement->getSizeBytes();
     for (uint32_t ct=0; ct < RS_MAX_ATTRIBS; ct++) {
         if (mGL.mUser[ct].size) {
-            va->setUser(mGL.mUser[ct], stride);
+            va->addUser(mGL.mUser[ct], stride);
         }
     }
 }
diff --git a/libs/rs/rsVertexArray.cpp b/libs/rs/rsVertexArray.cpp
index 7124eb5..a1fd744 100644
--- a/libs/rs/rsVertexArray.cpp
+++ b/libs/rs/rsVertexArray.cpp
@@ -25,7 +25,7 @@
 
 VertexArray::VertexArray()
 {
-    mActiveBuffer = 0;
+    clearAll();
 }
 
 VertexArray::~VertexArray()
@@ -39,6 +39,7 @@
         mAttribs[ct].clear();
     }
     mActiveBuffer = 0;
+    mCount = 0;
 }
 
 VertexArray::Attrib::Attrib()
@@ -54,6 +55,7 @@
     size = a.size;
     stride = a.stride;
     normalized = a.normalized;
+    kind = RS_KIND_USER;
     name.setTo(a.name);
 }
 
@@ -68,78 +70,42 @@
     name.setTo("");
 }
 
-void VertexArray::clear(AttribName n)
+void VertexArray::clear(uint32_t n)
 {
     mAttribs[n].clear();
 }
 
-void VertexArray::setPosition(uint32_t size, uint32_t type, uint32_t stride, uint32_t offset)
+void VertexArray::addUser(const Attrib &a, uint32_t stride)
 {
-    mAttribs[POSITION].buffer = mActiveBuffer;
-    mAttribs[POSITION].type = type;
-    mAttribs[POSITION].size = size;
-    mAttribs[POSITION].offset = offset;
-    mAttribs[POSITION].stride = stride;
-    mAttribs[POSITION].normalized = false;
+    assert(mCount < RS_MAX_ATTRIBS);
+    mAttribs[mCount].set(a);
+    mAttribs[mCount].buffer = mActiveBuffer;
+    mAttribs[mCount].stride = stride;
+    mAttribs[mCount].kind = RS_KIND_USER;
+    mCount ++;
 }
 
-void VertexArray::setColor(uint32_t size, uint32_t type, uint32_t stride, uint32_t offset)
+void VertexArray::addLegacy(uint32_t type, uint32_t size, uint32_t stride, RsDataKind kind, bool normalized, uint32_t offset)
 {
-    mAttribs[COLOR].buffer = mActiveBuffer;
-    mAttribs[COLOR].type = type;
-    mAttribs[COLOR].size = size;
-    mAttribs[COLOR].offset = offset;
-    mAttribs[COLOR].stride = stride;
-    mAttribs[COLOR].normalized = type != GL_FLOAT;
-}
-
-void VertexArray::setNormal(uint32_t type, uint32_t stride, uint32_t offset)
-{
-    mAttribs[NORMAL].buffer = mActiveBuffer;
-    mAttribs[NORMAL].type = type;
-    mAttribs[NORMAL].size = 3;
-    mAttribs[NORMAL].offset = offset;
-    mAttribs[NORMAL].stride = stride;
-    mAttribs[NORMAL].normalized = type != GL_FLOAT;
-}
-
-void VertexArray::setPointSize(uint32_t type, uint32_t stride, uint32_t offset)
-{
-    mAttribs[POINT_SIZE].buffer = mActiveBuffer;
-    mAttribs[POINT_SIZE].type = type;
-    mAttribs[POINT_SIZE].size = 1;
-    mAttribs[POINT_SIZE].offset = offset;
-    mAttribs[POINT_SIZE].stride = stride;
-    mAttribs[POINT_SIZE].normalized = false;
-}
-
-void VertexArray::setTexture(uint32_t size, uint32_t type, uint32_t stride, uint32_t offset)
-{
-    mAttribs[TEXTURE].buffer = mActiveBuffer;
-    mAttribs[TEXTURE].type = type;
-    mAttribs[TEXTURE].size = size;
-    mAttribs[TEXTURE].offset = offset;
-    mAttribs[TEXTURE].stride = stride;
-    mAttribs[TEXTURE].normalized = false;
-}
-
-void VertexArray::setUser(const Attrib &a, uint32_t stride)
-{
-    // Find empty slot, some may be taken by legacy 1.1 slots.
-    uint32_t slot = 0;
-    while (mAttribs[slot].size) slot++;
-    rsAssert(slot < RS_MAX_ATTRIBS);
-    mAttribs[slot].set(a);
-    mAttribs[slot].buffer = mActiveBuffer;
-    mAttribs[slot].stride = stride;
+    assert(mCount < RS_MAX_ATTRIBS);
+    mAttribs[mCount].clear();
+    mAttribs[mCount].type = type;
+    mAttribs[mCount].size = size;
+    mAttribs[mCount].offset = offset;
+    mAttribs[mCount].normalized = normalized;
+    mAttribs[mCount].buffer = mActiveBuffer;
+    mAttribs[mCount].stride = stride;
+    mAttribs[mCount].kind = kind;
+    mCount ++;
 }
 
 void VertexArray::logAttrib(uint32_t idx, uint32_t slot) const {
-    LOGE("va %i: slot=%i name=%s buf=%i  size=%i  type=0x%x  stride=0x%x  norm=%i  offset=0x%x", idx, slot,
+    LOGE("va %i: slot=%i name=%s buf=%i  size=%i  type=0x%x  kind=%i  stride=0x%x  norm=%i  offset=0x%x", idx, slot,
          mAttribs[idx].name.string(),
          mAttribs[idx].buffer,
          mAttribs[idx].size,
          mAttribs[idx].type,
+         mAttribs[idx].kind,
          mAttribs[idx].stride,
          mAttribs[idx].normalized,
          mAttribs[idx].offset);
@@ -147,87 +113,95 @@
 
 void VertexArray::setupGL(const Context *rsc, class VertexArrayState *state) const
 {
-    if (mAttribs[POSITION].size) {
-        //logAttrib(POSITION);
-        glEnableClientState(GL_VERTEX_ARRAY);
-        glBindBuffer(GL_ARRAY_BUFFER, mAttribs[POSITION].buffer);
-        glVertexPointer(mAttribs[POSITION].size,
-                        mAttribs[POSITION].type,
-                        mAttribs[POSITION].stride,
-                        (void *)mAttribs[POSITION].offset);
-    } else {
-        rsAssert(0);
-    }
-
-    if (mAttribs[NORMAL].size) {
-        //logAttrib(NORMAL);
-        glEnableClientState(GL_NORMAL_ARRAY);
-        rsAssert(mAttribs[NORMAL].size == 3);
-        glBindBuffer(GL_ARRAY_BUFFER, mAttribs[NORMAL].buffer);
-        glNormalPointer(mAttribs[NORMAL].type,
-                        mAttribs[NORMAL].stride,
-                        (void *)mAttribs[NORMAL].offset);
-    } else {
-        glDisableClientState(GL_NORMAL_ARRAY);
-    }
-
-    if (mAttribs[COLOR].size) {
-        //logAttrib(COLOR);
-        glEnableClientState(GL_COLOR_ARRAY);
-        glBindBuffer(GL_ARRAY_BUFFER, mAttribs[COLOR].buffer);
-        glColorPointer(mAttribs[COLOR].size,
-                       mAttribs[COLOR].type,
-                       mAttribs[COLOR].stride,
-                       (void *)mAttribs[COLOR].offset);
-    } else {
-        glDisableClientState(GL_COLOR_ARRAY);
-    }
-
     glClientActiveTexture(GL_TEXTURE0);
-    if (mAttribs[TEXTURE].size) {
-        //logAttrib(TEXTURE);
-        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
-        glBindBuffer(GL_ARRAY_BUFFER, mAttribs[TEXTURE].buffer);
-        glTexCoordPointer(mAttribs[TEXTURE].size,
-                          mAttribs[TEXTURE].type,
-                          mAttribs[TEXTURE].stride,
-                          (void *)mAttribs[TEXTURE].offset);
-    } else {
-        glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    glDisableClientState(GL_NORMAL_ARRAY);
+    glDisableClientState(GL_COLOR_ARRAY);
+    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
+    glDisableClientState(GL_POINT_SIZE_ARRAY_OES);
+
+    for (uint32_t ct=0; ct < mCount; ct++) {
+        switch(mAttribs[ct].kind) {
+        case RS_KIND_POSITION:
+            //logAttrib(POSITION);
+            glEnableClientState(GL_VERTEX_ARRAY);
+            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
+            glVertexPointer(mAttribs[ct].size,
+                            mAttribs[ct].type,
+                            mAttribs[ct].stride,
+                            (void *)mAttribs[ct].offset);
+            break;
+
+        case RS_KIND_NORMAL:
+            //logAttrib(NORMAL);
+            glEnableClientState(GL_NORMAL_ARRAY);
+            rsAssert(mAttribs[ct].size == 3);
+            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
+            glNormalPointer(mAttribs[ct].type,
+                            mAttribs[ct].stride,
+                            (void *)mAttribs[ct].offset);
+            break;
+
+        case RS_KIND_COLOR:
+            //logAttrib(COLOR);
+            glEnableClientState(GL_COLOR_ARRAY);
+            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
+            glColorPointer(mAttribs[ct].size,
+                           mAttribs[ct].type,
+                           mAttribs[ct].stride,
+                           (void *)mAttribs[ct].offset);
+            break;
+
+        case RS_KIND_TEXTURE:
+            //logAttrib(TEXTURE);
+            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
+            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
+            glTexCoordPointer(mAttribs[ct].size,
+                              mAttribs[ct].type,
+                              mAttribs[ct].stride,
+                              (void *)mAttribs[ct].offset);
+            break;
+
+        case RS_KIND_POINT_SIZE:
+            //logAttrib(POINT_SIZE);
+            glEnableClientState(GL_POINT_SIZE_ARRAY_OES);
+            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
+            glPointSizePointerOES(mAttribs[ct].type,
+                                  mAttribs[ct].stride,
+                                  (void *)mAttribs[ct].offset);
+            break;
+
+        default:
+            rsAssert(0);
+        }
     }
 
-    if (mAttribs[POINT_SIZE].size) {
-        //logAttrib(POINT_SIZE);
-        glEnableClientState(GL_POINT_SIZE_ARRAY_OES);
-        glBindBuffer(GL_ARRAY_BUFFER, mAttribs[POINT_SIZE].buffer);
-        glPointSizePointerOES(mAttribs[POINT_SIZE].type,
-                              mAttribs[POINT_SIZE].stride,
-                              (void *)mAttribs[POINT_SIZE].offset);
-    } else {
-        glDisableClientState(GL_POINT_SIZE_ARRAY_OES);
-    }
     rsc->checkError("VertexArray::setupGL");
 }
 
 void VertexArray::setupGL2(const Context *rsc, class VertexArrayState *state, ShaderCache *sc) const
 {
-    for (int ct=1; ct < _LAST; ct++) {
+    for (int ct=1; ct < RS_MAX_ATTRIBS; ct++) {
         glDisableVertexAttribArray(ct);
     }
 
-    for (uint32_t ct=0; ct < RS_MAX_ATTRIBS; ct++) {
-        if (mAttribs[ct].size && (sc->vtxAttribSlot(ct) >= 0)) {
-            //logAttrib(ct, sc->vtxAttribSlot(ct));
-            glEnableVertexAttribArray(sc->vtxAttribSlot(ct));
-            glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
-
-            glVertexAttribPointer(sc->vtxAttribSlot(ct),
-                                  mAttribs[ct].size,
-                                  mAttribs[ct].type,
-                                  mAttribs[ct].normalized,
-                                  mAttribs[ct].stride,
-                                  (void *)mAttribs[ct].offset);
+    for (uint32_t ct=0; ct < mCount; ct++) {
+        uint32_t slot = 0;
+        if (sc->isUserVertexProgram()) {
+            slot = sc->vtxAttribSlot(ct);
+        } else {
+            slot = sc->vtxAttribSlot(mAttribs[ct].kind);
         }
+
+        //logAttrib(ct, slot);
+        glEnableVertexAttribArray(slot);
+        glBindBuffer(GL_ARRAY_BUFFER, mAttribs[ct].buffer);
+
+        glVertexAttribPointer(slot,
+                              mAttribs[ct].size,
+                              mAttribs[ct].type,
+                              mAttribs[ct].normalized,
+                              mAttribs[ct].stride,
+                              (void *)mAttribs[ct].offset);
     }
     rsc->checkError("VertexArray::setupGL2");
 }
diff --git a/libs/rs/rsVertexArray.h b/libs/rs/rsVertexArray.h
index 26e6f84..66b3ab00 100644
--- a/libs/rs/rsVertexArray.h
+++ b/libs/rs/rsVertexArray.h
@@ -33,14 +33,6 @@
     VertexArray();
     virtual ~VertexArray();
 
-    enum AttribName {
-        POSITION,
-        COLOR,
-        NORMAL,
-        POINT_SIZE,
-        TEXTURE,
-        _LAST
-    };
 
     class Attrib {
     public:
@@ -51,6 +43,7 @@
         uint32_t stride;
         bool normalized;
         String8 name;
+        RsDataKind kind;
 
         Attrib();
         void set(const Attrib &);
@@ -59,23 +52,19 @@
 
 
     void clearAll();
-    void clear(AttribName);
-
     void setActiveBuffer(uint32_t id) {mActiveBuffer = id;}
-
-    void setUser(const Attrib &, uint32_t stride);
-    void setPosition(uint32_t size, uint32_t type, uint32_t stride, uint32_t offset);
-    void setColor(uint32_t size, uint32_t type, uint32_t stride, uint32_t offset);
-    void setNormal(uint32_t type, uint32_t stride, uint32_t offset);
-    void setPointSize(uint32_t type, uint32_t stride, uint32_t offset);
-    void setTexture(uint32_t size, uint32_t type, uint32_t stride, uint32_t offset);
+    void addUser(const Attrib &, uint32_t stride);
+    void addLegacy(uint32_t type, uint32_t size, uint32_t stride, RsDataKind kind, bool normalized, uint32_t offset);
 
     void setupGL(const Context *rsc, class VertexArrayState *) const;
     void setupGL2(const Context *rsc, class VertexArrayState *, ShaderCache *) const;
     void logAttrib(uint32_t idx, uint32_t slot) const;
 
 protected:
+    void clear(uint32_t index);
     uint32_t mActiveBuffer;
+    uint32_t mCount;
+
     Attrib mAttribs[RS_MAX_ATTRIBS];
 };
 
diff --git a/libs/ui/CameraParameters.cpp b/libs/ui/CameraParameters.cpp
index 8f1749d..2e0409b 100644
--- a/libs/ui/CameraParameters.cpp
+++ b/libs/ui/CameraParameters.cpp
@@ -36,7 +36,7 @@
 const char CameraParameters::KEY_SUPPORTED_PICTURE_FORMATS[] = "picture-format-values";
 const char CameraParameters::KEY_JPEG_THUMBNAIL_WIDTH[] = "jpeg-thumbnail-width";
 const char CameraParameters::KEY_JPEG_THUMBNAIL_HEIGHT[] = "jpeg-thumbnail-height";
-const char CameraParameters::KEY_SUPPORTED_THUMBNAIL_SIZES[] = "jpeg-thumbnail-size-values";
+const char CameraParameters::KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES[] = "jpeg-thumbnail-size-values";
 const char CameraParameters::KEY_JPEG_THUMBNAIL_QUALITY[] = "jpeg-thumbnail-quality";
 const char CameraParameters::KEY_JPEG_QUALITY[] = "jpeg-quality";
 const char CameraParameters::KEY_ROTATION[] = "rotation";
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index 5352234..e1b1776 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -348,6 +348,9 @@
     Mutex::Autolock _l(AudioSystem::gLock);
 
     AudioSystem::gAudioFlinger.clear();
+    // clear output handles and stream to output map caches
+    AudioSystem::gStreamOutputMap.clear();
+    AudioSystem::gOutputs.clear();
 
     if (gAudioErrorCallback) {
         gAudioErrorCallback(DEAD_OBJECT);
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/libagl/texture.cpp b/opengl/libagl/texture.cpp
index 13d078e..2875c13 100644
--- a/opengl/libagl/texture.cpp
+++ b/opengl/libagl/texture.cpp
@@ -583,7 +583,7 @@
 
 
 static __attribute__((noinline))
-void set_depth_and_fog(ogles_context_t* c, GLint z)
+void set_depth_and_fog(ogles_context_t* c, GGLfixed z)
 {
     const uint32_t enables = c->rasterizer.state.enables;
     // we need to compute Zw
@@ -592,8 +592,8 @@
     GGLfixed Zw;
     GGLfixed n = gglFloatToFixed(c->transforms.vpt.zNear);
     GGLfixed f = gglFloatToFixed(c->transforms.vpt.zFar);
-    if (z<=0)       Zw = n;
-    else if (z>=1)  Zw = f;
+    if (z<=0)               Zw = n;
+    else if (z>=0x10000)    Zw = f;
     else            Zw = gglMulAddx(z, (f-n), n);
     if (enables & GGL_ENABLE_FOG) {
         // set up fog if needed...
@@ -836,7 +836,7 @@
             c->rasterizer.procs.texCoord2i(c, s0, t0);
             const uint32_t enables = c->rasterizer.state.enables;
             if (ggl_unlikely(enables & (GGL_ENABLE_DEPTH_TEST|GGL_ENABLE_FOG)))
-                set_depth_and_fog(c, z);
+                set_depth_and_fog(c, gglIntToFixed(z));
 
             c->rasterizer.procs.color4xv(c, c->currentColorClamped.v);
             c->rasterizer.procs.disable(c, GGL_W_LERP);
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;
         }
     }