OSDN Git Service

First draft of multitouch in the WebView.
authorGrace Kloba <klobag@google.com>
Wed, 6 Jan 2010 23:49:29 +0000 (15:49 -0800)
committerGrace Kloba <klobag@google.com>
Mon, 11 Jan 2010 18:41:40 +0000 (10:41 -0800)
Currently we just handle a simple pinch action. We
will wait for framework support for more complicated
gesture.

When pinch in the webview, zoom level will be changed
on the fly. But we won't re-wrap the text until user
action like double tap, rotate screen.

Double tap will re-layout the page and wrap the text
to the screen width. We try to keep the spot you
tapped at the same place on the screen after relayout.
If the block after relayout fully fit on the current
screen, we will center it for easy reading.

Fix http://b/issue?id=2360032

core/java/android/webkit/WebView.java
core/java/android/webkit/WebViewCore.java

index ed1a98a..8308fd1 100644 (file)
@@ -21,6 +21,7 @@ import android.content.Context;
 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;
@@ -349,6 +350,7 @@ public class WebView extends AbsoluteLayout
     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 +462,7 @@ public class WebView extends AbsoluteLayout
     // obj=Rect in doc coordinates
     static final int INVAL_RECT_MSG_ID                  = 26;
     static final int REQUEST_KEYBOARD                   = 27;
+    static final int SHOW_RECT_MSG_ID                   = 28;
 
     static final String[] HandlerDebugString = {
         "REMEMBER_PASSWORD", //              = 1;
@@ -488,7 +491,8 @@ public class WebView extends AbsoluteLayout
         "PREVENT_TOUCH_ID", //               = 24;
         "WEBCORE_NEED_TOUCH_EVENTS", //      = 25;
         "INVAL_RECT_MSG_ID", //              = 26;
-        "REQUEST_KEYBOARD" //                = 27;
+        "REQUEST_KEYBOARD", //               = 27;
+        "SHOW_RECT_MSG_ID" //                = 28;
     };
 
     // default scale limit. Depending on the display density
@@ -511,7 +515,7 @@ public class WebView extends AbsoluteLayout
     // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn,
     // engadget always have wider mContentWidth no matter what viewport size is.
     int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH;
-    float mLastScale;
+    float mTextWrapScale;
 
     // default scale. Depending on the display density.
     static int DEFAULT_SCALE_PERCENT;
@@ -743,6 +747,9 @@ public class WebView extends AbsoluteLayout
                     params;
             frameParams.gravity = Gravity.RIGHT;
         }
+
+        mSupportMultiTouch = context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH);
     }
 
     private void updateZoomButtonsEnabled() {
@@ -785,6 +792,7 @@ public class WebView extends AbsoluteLayout
         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;
@@ -805,7 +813,7 @@ public class WebView extends AbsoluteLayout
             mDefaultScale = density;
             mMaxZoomScale *= scaleFactor;
             mMinZoomScale *= scaleFactor;
-            setNewZoomScale(mActualScale * scaleFactor, false);
+            setNewZoomScale(mActualScale * scaleFactor, true, false);
         }
     }
 
@@ -1162,9 +1170,8 @@ public class WebView extends AbsoluteLayout
             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;
@@ -1209,13 +1216,8 @@ public class WebView extends AbsoluteLayout
                 // 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;
             }
@@ -1938,12 +1940,18 @@ public class WebView extends AbsoluteLayout
         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
@@ -1953,9 +1961,7 @@ public class WebView extends AbsoluteLayout
                 }
                 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
@@ -1983,10 +1989,9 @@ public class WebView extends AbsoluteLayout
                 mScrollX = pinLocX(Math.round(sx));
                 mScrollY = pinLocY(Math.round(sy));
 
-                if (!mPreviewZoomOnly) {
-                    sendViewSizeZoom();
-                    sendOurVisibleRect();
-                }
+                // update webkit
+                sendViewSizeZoom();
+                sendOurVisibleRect();
             }
         }
     }
@@ -1996,6 +2001,8 @@ public class WebView extends AbsoluteLayout
     private Rect mLastGlobalRect;
 
     private Rect sendOurVisibleRect() {
+        if (mPreviewZoomOnly) return mLastVisibleRectSent;
+
         Rect rect = new Rect();
         calcOurContentVisibleRect(rect);
         // Rect.equals() checks for null input.
@@ -2049,6 +2056,8 @@ public class WebView extends AbsoluteLayout
         int mWidth;
         int mHeight;
         int mTextWrapWidth;
+        int mAnchorX;
+        int mAnchorY;
         float mScale;
         boolean mIgnoreHeight;
     }
@@ -2060,6 +2069,8 @@ public class WebView extends AbsoluteLayout
      * @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);
@@ -2079,13 +2090,11 @@ public class WebView extends AbsoluteLayout
             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;
@@ -2838,6 +2847,38 @@ public class WebView extends AbsoluteLayout
      */
     private boolean mNeedToAdjustWebTextView;
 
+    // if checkVisibility is false, the WebTextView may trigger a move of
+    // WebView to bring itself into the view.
+    private void adjustTextView(boolean checkVisibility) {
+        Rect contentBounds = nativeFocusCandidateNodeBounds();
+        Rect vBox = contentToViewRect(contentBounds);
+        Rect visibleRect = new Rect();
+        calcOurVisibleRect(visibleRect);
+        if (!checkVisibility || visibleRect.contains(vBox)) {
+            // As a result of the zoom, the textfield is now on
+            // screen. Place the WebTextView in its new place,
+            // accounting for our new scroll/zoom values.
+            mWebTextView
+                    .setTextSize(
+                            TypedValue.COMPLEX_UNIT_PX,
+                            contentToViewDimension(nativeFocusCandidateTextSize()));
+            mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
+                    vBox.height());
+            // If it is a password field, start drawing the
+            // WebTextView once again.
+            if (nativeFocusCandidateIsPassword()) {
+                mWebTextView.setInPassword(true);
+            }
+        } else {
+            // The textfield is now off screen. The user probably
+            // was not zooming to see the textfield better. Remove
+            // the WebTextView. If the user types a key, and the
+            // textfield is still in focus, we will reconstruct
+            // the WebTextView and scroll it back on screen.
+            mWebTextView.remove();
+        }
+    }
+
     private void drawCoreAndCursorRing(Canvas canvas, int color,
         boolean drawCursorRing) {
         if (mDrawHistory) {
@@ -2865,32 +2906,7 @@ public class WebView extends AbsoluteLayout
                 invalidate();
                 if (mNeedToAdjustWebTextView) {
                     mNeedToAdjustWebTextView = false;
-                    Rect contentBounds = nativeFocusCandidateNodeBounds();
-                    Rect vBox = contentToViewRect(contentBounds);
-                    Rect visibleRect = new Rect();
-                    calcOurVisibleRect(visibleRect);
-                    if (visibleRect.contains(vBox)) {
-                        // As a result of the zoom, the textfield is now on
-                        // screen.  Place the WebTextView in its new place,
-                        // accounting for our new scroll/zoom values.
-                        mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX,
-                                contentToViewDimension(
-                                nativeFocusCandidateTextSize()));
-                        mWebTextView.setRect(vBox.left, vBox.top, vBox.width(),
-                                vBox.height());
-                        // If it is a password field, start drawing the
-                        // WebTextView once again.
-                        if (nativeFocusCandidateIsPassword()) {
-                            mWebTextView.setInPassword(true);
-                        }
-                    } else {
-                        // The textfield is now off screen.  The user probably
-                        // was not zooming to see the textfield better.  Remove
-                        // the WebTextView.  If the user types a key, and the
-                        // textfield is still in focus, we will reconstruct
-                        // the WebTextView and scroll it back on screen.
-                        mWebTextView.remove();
-                    }
+                    adjustTextView(true);
                 }
             }
             // calculate the intermediate scroll position. As we need to use
@@ -2924,11 +2940,11 @@ public class WebView extends AbsoluteLayout
             canvas.scale(mActualScale, mActualScale);
         }
 
-        mWebViewCore.drawContentPicture(canvas, color, animateZoom,
-                animateScroll);
+        mWebViewCore.drawContentPicture(canvas, color,
+                (animateZoom || mPreviewZoomOnly), animateScroll);
 
         if (mNativeClass == 0) return;
-        if (mShiftIsPressed && !animateZoom) {
+        if (mShiftIsPressed && !(animateZoom || mPreviewZoomOnly)) {
             if (mTouchSelection) {
                 nativeDrawSelectionRegion(canvas);
             } else {
@@ -3029,10 +3045,15 @@ public class WebView extends AbsoluteLayout
             if (mWebTextView == null) 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();
+            if (mActualScale < mDefaultScale) {
+                // bring it back to the default scale so that user can enter
+                // text.
+                mInZoomOverview = false;
+                mZoomCenterX = mLastTouchX;
+                mZoomCenterY = mLastTouchY;
+                // do not change text wrap scale so that there is no reflow
+                setNewZoomScale(mDefaultScale, false, false);
+                adjustTextView(false);
             }
         }
         else { // used by plugins
@@ -3604,6 +3625,8 @@ public class WebView extends AbsoluteLayout
         if (mZoomScale == 0) { // unless we're already zooming
             mZoomCenterX = getViewWidth() * .5f;
             mZoomCenterY = getViewHeight() * .5f;
+            mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+            mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
         }
 
         // update mMinZoomScale if the minimum zoom scale is not fixed
@@ -3626,7 +3649,8 @@ public class WebView extends AbsoluteLayout
 
         // we always force, in case our height changed, in which case we still
         // want to send the notification over to webkit
-        setNewZoomScale(mActualScale, true);
+        // only update the text wrap scale if width changed.
+        setNewZoomScale(mActualScale, w != ow, true);
     }
 
     @Override
@@ -3674,6 +3698,97 @@ public class WebView extends AbsoluteLayout
     private static final float MAX_SLOPE_FOR_DIAG = 1.5f;
     private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80;
 
+    // MultiTouch handling
+    private static boolean mSupportMultiTouch;
+
+    private double mPinchDistance;
+    private int mAnchorX;
+    private int mAnchorY;
+
+    private boolean doMultiTouch(MotionEvent ev) {
+        int action = ev.getAction();
+
+        if ((action & 0xff) == MotionEvent.ACTION_POINTER_DOWN) {
+            // cancel the single touch handling
+            cancelTouch();
+            // 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);
+            }
+            // start multi (2-pointer) touch
+            mPreviewZoomOnly = true;
+            float x0 = ev.getX(0);
+            float y0 = ev.getY(0);
+            float x1 = ev.getX(1);
+            float y1 = ev.getY(1);
+            mLastTouchTime = ev.getEventTime();
+            mPinchDistance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
+                    * (y0 - y1));
+            mZoomCenterX = mZoomCenterY = 0;
+        } else if ((action & 0xff) == MotionEvent.ACTION_POINTER_UP) {
+            mPreviewZoomOnly = false;
+            // for testing only, default don't reflow now
+            boolean reflowNow = !getSettings().getPluginsEnabled();
+            // force zoom after mPreviewZoomOnly is set to false so that the new
+            // view size will be passed to the WebKit
+            if (reflowNow && (mZoomCenterX != 0) && (mZoomCenterY != 0)) {
+                mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX);
+                mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY);
+            }
+            setNewZoomScale(mActualScale, reflowNow, true);
+            // call invalidate() to draw without zoom filter
+            invalidate();
+            // adjust the edit text view if needed
+            if (inEditingMode()) {
+                adjustTextView(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;
+            // action indicates which pointer is UP. Use the other one as drag's
+            // starting position.
+            int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK)
+                    >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0;
+            startTouch(ev.getX(id), ev.getY(id), ev.getEventTime());
+        } else if (action == MotionEvent.ACTION_MOVE) {
+            float x0 = ev.getX(0);
+            float y0 = ev.getY(0);
+            float x1 = ev.getX(1);
+            float y1 = ev.getY(1);
+            double distance = Math.sqrt((x0 - x1) * (x0 - x1) + (y0 - y1)
+                    * (y0 - y1));
+            float scale = (float) (Math.round(distance / mPinchDistance
+                    * mActualScale * 100) / 100.0);
+            long time = ev.getEventTime();
+            // add distance/time checking to avoid the minor shift right before
+            // lifting the fingers after a pause
+            if (Math.abs(scale - mActualScale) >= 0.01f
+                    && ((time - mLastTouchTime) < 300 || Math.abs(distance
+                            - mPinchDistance) > (10 * mDefaultScale))) {
+                // 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 = (x0 + x1) / 2;
+                mZoomCenterY = (y0 + y1) / 2;
+                setNewZoomScale(scale, false, false);
+                invalidate();
+                mPinchDistance = distance;
+                mLastTouchTime = time;
+            }
+        } else {
+            Log.w(LOGTAG, action + " should not happen during doMultiTouch");
+            return false;
+        }
+        return true;
+    }
+
     @Override
     public boolean onTouchEvent(MotionEvent ev) {
         if (mNativeClass == 0 || !isClickable() || !isLongClickable()) {
@@ -3685,6 +3800,11 @@ public class WebView extends AbsoluteLayout
                     + mTouchMode);
         }
 
+        if (mSupportMultiTouch && getSettings().supportZoom()
+                && ev.getPointerCount() > 1) {
+            return doMultiTouch(ev);
+        }
+
         int action = ev.getAction();
         float x = ev.getX();
         float y = ev.getY();
@@ -3745,6 +3865,7 @@ public class WebView extends AbsoluteLayout
                         // continue, mTouchMode should be still TOUCH_INIT_MODE
                     }
                 } else {
+                    mPreviewZoomOnly = false;
                     mTouchMode = TOUCH_INIT_MODE;
                     mPreventDrag = mForwardTouchEvents ? PREVENT_DRAG_MAYBE_YES
                             : PREVENT_DRAG_NO;
@@ -3762,11 +3883,7 @@ public class WebView extends AbsoluteLayout
                             .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT);
                 }
                 // Remember where the motion event started
-                mLastTouchX = x;
-                mLastTouchY = y;
-                mLastTouchTime = eventTime;
-                mVelocityTracker = VelocityTracker.obtain();
-                mSnapScrollMode = SNAP_NONE;
+                startTouch(x, y, eventTime);
                 break;
             }
             case MotionEvent.ACTION_MOVE: {
@@ -3821,18 +3938,20 @@ public class WebView extends AbsoluteLayout
                     if (!mDragFromTextInput) {
                         nativeHideCursor();
                     }
-                    WebSettings settings = getSettings();
-                    if (settings.supportZoom()
-                            && settings.getBuiltInZoomControls()
-                            && !mZoomButtonsController.isVisible()
-                            && mMinZoomScale < mMaxZoomScale) {
-                        mZoomButtonsController.setVisible(true);
-                        int count = settings.getDoubleTapToastCount();
-                        if (mInZoomOverview && count > 0) {
-                            settings.setDoubleTapToastCount(--count);
-                            Toast.makeText(mContext,
-                                    com.android.internal.R.string.double_tap_toast,
-                                    Toast.LENGTH_LONG).show();
+                    if (!mSupportMultiTouch) {
+                        WebSettings settings = getSettings();
+                        if (settings.supportZoom()
+                                && settings.getBuiltInZoomControls()
+                                && !mZoomButtonsController.isVisible()
+                                && mMinZoomScale < mMaxZoomScale) {
+                            mZoomButtonsController.setVisible(true);
+                            int count = settings.getDoubleTapToastCount();
+                            if (mInZoomOverview && count > 0) {
+                                settings.setDoubleTapToastCount(--count);
+                                Toast.makeText(mContext,
+                                        com.android.internal.R.string.double_tap_toast,
+                                        Toast.LENGTH_LONG).show();
+                            }
                         }
                     }
                 }
@@ -3911,7 +4030,8 @@ public class WebView extends AbsoluteLayout
                     mUserScroll = true;
                 }
 
-                if (!getSettings().getBuiltInZoomControls()) {
+                if (!mSupportMultiTouch
+                        && !getSettings().getBuiltInZoomControls()) {
                     boolean showPlusMinus = mMinZoomScale < mMaxZoomScale;
                     if (mZoomControls != null && showPlusMinus) {
                         if (mZoomControls.getVisibility() == View.VISIBLE) {
@@ -4010,26 +4130,38 @@ public class WebView extends AbsoluteLayout
                 break;
             }
             case MotionEvent.ACTION_CANCEL: {
-                // 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);
-                mTouchMode = TOUCH_DONE_MODE;
-                nativeHideCursor();
+                cancelTouch();
                 break;
             }
         }
         return true;
     }
 
+    private void startTouch(float x, float y, long eventTime) {
+        mLastTouchX = x;
+        mLastTouchY = y;
+        mLastTouchTime = eventTime;
+        mVelocityTracker = VelocityTracker.obtain();
+        mSnapScrollMode = SNAP_NONE;
+    }
+
+    private void cancelTouch() {
+        // 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);
+        mTouchMode = TOUCH_DONE_MODE;
+        nativeHideCursor();
+    }
+
     private long mTrackballFirstTime = 0;
     private long mTrackballLastTime = 0;
     private float mTrackballRemainsX = 0.0f;
@@ -4389,7 +4521,7 @@ public class WebView extends AbsoluteLayout
             scale = mDefaultScale;
         }
 
-        setNewZoomScale(scale, false);
+        setNewZoomScale(scale, true, false);
 
         if (oldScale != mActualScale) {
             // use mZoomPickerScale to see zoom preview first
@@ -4397,9 +4529,6 @@ public class WebView extends AbsoluteLayout
             mInvInitialZoomScale = 1.0f / oldScale;
             mInvFinalZoomScale = 1.0f / mActualScale;
             mZoomScale = mActualScale;
-            if (!mInZoomOverview) {
-                mLastScale = scale;
-            }
             invalidate();
             return true;
         } else {
@@ -4497,18 +4626,13 @@ public class WebView extends AbsoluteLayout
     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);
     }
 
     /**
@@ -4518,18 +4642,12 @@ public class WebView extends AbsoluteLayout
     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()) {
-            // 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() {
@@ -4645,44 +4763,71 @@ public class WebView extends AbsoluteLayout
         }
     }
 
+    // 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();
-        if (settings.getBuiltInZoomControls()) {
-            if (mZoomButtonsController.isVisible()) {
-                mZoomButtonsController.setVisible(false);
+        if (!mSupportMultiTouch) {
+            // remove the zoom control after double tap
+            if (settings.getBuiltInZoomControls()) {
+                if (mZoomButtonsController.isVisible()) {
+                    mZoomButtonsController.setVisible(false);
+                }
+            } else {
+                if (mZoomControlRunnable != null) {
+                    mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+                }
+                if (mZoomControls != null) {
+                    mZoomControls.hide();
+                }
             }
-        } else {
-            if (mZoomControlRunnable != null) {
-                mPrivateHandler.removeCallbacks(mZoomControlRunnable);
+            settings.setDoubleTapToastCount(0);
+        }
+        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;
             }
-            if (mZoomControls != null) {
-                mZoomControls.hide();
+        } else if (!mInZoomOverview) {
+            float newScale = (float) getViewWidth() / mZoomOverviewWidth;
+            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) {
+                mInZoomOverview = true;
             }
-        }
-        settings.setDoubleTapToastCount(0);
-        if (mInZoomOverview) {
-            // Force the titlebar fully reveal in overview mode
-            if (mScrollY < getTitleHeight()) mScrollY = 0;
-            zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth);
         } 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);
+            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);
         }
     }
 
@@ -4888,9 +5033,10 @@ public class WebView extends AbsoluteLayout
         @Override
         public void handleMessage(Message msg) {
             if (DebugFlags.WEB_VIEW) {
-                Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what
-                        > INVAL_RECT_MSG_ID ? Integer.toString(msg.what)
-                        : HandlerDebugString[msg.what - REMEMBER_PASSWORD]);
+                Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD
+                        || msg.what > SHOW_RECT_MSG_ID ? Integer
+                        .toString(msg.what) : HandlerDebugString[msg.what
+                        - REMEMBER_PASSWORD]);
             }
             if (mWebViewCore == null) {
                 // after WebView's destroy() is called, skip handling messages.
@@ -4975,9 +5121,6 @@ public class WebView extends AbsoluteLayout
                     WebViewCore.RestoreState restoreState = draw.mRestoreState;
                     if (restoreState != null) {
                         mInZoomOverview = false;
-                        mLastScale = mInitialScaleInPercent > 0
-                                ? mInitialScaleInPercent / 100.0f
-                                        : restoreState.mTextWrapScale;
                         if (restoreState.mMinScale == 0) {
                             if (restoreState.mMobileSite) {
                                 if (draw.mMinPrefWidth >
@@ -4985,6 +5128,8 @@ public class WebView extends AbsoluteLayout
                                     mMinZoomScale = (float) viewWidth
                                             / draw.mMinPrefWidth;
                                     mMinZoomScaleFixed = false;
+                                    mInZoomOverview = useWideViewport &&
+                                            settings.getLoadWithOverviewMode();
                                 } else {
                                     mMinZoomScale = restoreState.mDefaultScale;
                                     mMinZoomScaleFixed = true;
@@ -5002,17 +5147,27 @@ public class WebView extends AbsoluteLayout
                         } else {
                             mMaxZoomScale = restoreState.mMaxScale;
                         }
-                        setNewZoomScale(mLastScale, false);
-                        setContentScrollTo(restoreState.mScrollX,
-                                restoreState.mScrollY);
-                        if (useWideViewport
-                                && settings.getLoadWithOverviewMode()) {
-                            if (restoreState.mViewScale == 0
-                                    || (restoreState.mMobileSite
-                                    && mMinZoomScale < restoreState.mDefaultScale)) {
-                                mInZoomOverview = true;
+                        if (mInitialScaleInPercent > 0) {
+                            setNewZoomScale(mInitialScaleInPercent / 100.0f,
+                                    true, false);
+                        } else if (restoreState.mViewScale > 0) {
+                            mTextWrapScale = restoreState.mTextWrapScale;
+                            setNewZoomScale(restoreState.mViewScale, false,
+                                    false);
+                        } else {
+                            mInZoomOverview = useWideViewport
+                                    && settings.getLoadWithOverviewMode();
+                            if (mInZoomOverview) {
+                                setNewZoomScale((float) viewWidth
+                                        / WebViewCore.DEFAULT_VIEWPORT_WIDTH,
+                                        true, false);
+                            } else {
+                                setNewZoomScale(restoreState.mTextWrapScale,
+                                        true, false);
                             }
                         }
+                        setContentScrollTo(restoreState.mScrollX,
+                                restoreState.mScrollY);
                         // 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
@@ -5050,7 +5205,7 @@ public class WebView extends AbsoluteLayout
                         if (Math.abs((viewWidth * mInvActualScale)
                                 - mZoomOverviewWidth) > 1) {
                             setNewZoomScale((float) viewWidth
-                                    / mZoomOverviewWidth, false);
+                                    / mZoomOverviewWidth, true, false);
                         }
                     }
                     break;
@@ -5182,6 +5337,43 @@ public class WebView extends AbsoluteLayout
                     }
                     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;
index a5a4852..27b67d1 100644 (file)
@@ -447,8 +447,8 @@ final class WebViewCore {
         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();
 
@@ -961,6 +961,7 @@ final class WebViewCore {
                                     (WebView.ViewSizeData) msg.obj;
                             viewSizeChanged(data.mWidth, data.mHeight,
                                     data.mTextWrapWidth, data.mScale,
+                                    data.mAnchorX, data.mAnchorY,
                                     data.mIgnoreHeight);
                             break;
                         }
@@ -1483,7 +1484,7 @@ final class WebViewCore {
 
     // 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);
@@ -1514,12 +1515,14 @@ final class WebViewCore {
                     width = Math.max(w, Math.max(DEFAULT_VIEWPORT_WIDTH,
                             nativeGetContentMinPrefWidth()));
                 }
-            } else {
+            } else if (mViewportWidth > 0) {
                 width = Math.max(w, mViewportWidth);
+            } else {
+                width = Math.max(w, nativeGetContentMinPrefWidth());
             }
         }
         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;
@@ -1953,14 +1956,12 @@ final class WebViewCore {
         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) {
@@ -1993,6 +1994,7 @@ final class WebViewCore {
             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,
@@ -2021,6 +2023,7 @@ final class WebViewCore {
                 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);
@@ -2177,6 +2180,40 @@ final class WebViewCore {
         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();