Home | History | Annotate | Download | only in webkit
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.webkit;
     18 
     19 import android.content.Context;
     20 import android.content.pm.PackageManager;
     21 import android.graphics.Canvas;
     22 import android.graphics.Point;
     23 import android.graphics.Rect;
     24 import android.os.Bundle;
     25 import android.os.SystemClock;
     26 import android.util.FloatMath;
     27 import android.util.Log;
     28 import android.view.ScaleGestureDetector;
     29 import android.view.View;
     30 
     31 /**
     32  * The ZoomManager is responsible for maintaining the WebView's current zoom
     33  * level state.  It is also responsible for managing the on-screen zoom controls
     34  * as well as any animation of the WebView due to zooming.
     35  *
     36  * Currently, there are two methods for animating the zoom of a WebView.
     37  *
     38  * (1) The first method is triggered by startZoomAnimation(...) and is a fixed
     39  * length animation where the final zoom scale is known at startup.  This type of
     40  * animation notifies webkit of the final scale BEFORE it animates. The animation
     41  * is then done by scaling the CANVAS incrementally based on a stepping function.
     42  *
     43  * (2) The second method is triggered by a multi-touch pinch and the new scale
     44  * is determined dynamically based on the user's gesture. This type of animation
     45  * only notifies webkit of new scale AFTER the gesture is complete. The animation
     46  * effect is achieved by scaling the VIEWS (both WebView and ViewManager.ChildView)
     47  * to the new scale in response to events related to the user's gesture.
     48  */
     49 class ZoomManager {
     50 
     51     static final String LOGTAG = "webviewZoom";
     52 
     53     private final WebView mWebView;
     54     private final CallbackProxy mCallbackProxy;
     55 
     56     // Widgets responsible for the on-screen zoom functions of the WebView.
     57     private ZoomControlEmbedded mEmbeddedZoomControl;
     58     private ZoomControlExternal mExternalZoomControl;
     59 
     60     /*
     61      * The scale factors that determine the upper and lower bounds for the
     62      * default zoom scale.
     63      */
     64     protected static final float DEFAULT_MAX_ZOOM_SCALE_FACTOR = 4.00f;
     65     protected static final float DEFAULT_MIN_ZOOM_SCALE_FACTOR = 0.25f;
     66 
     67     // The default scale limits, which are dependent on the display density.
     68     private float mDefaultMaxZoomScale;
     69     private float mDefaultMinZoomScale;
     70 
     71     // The actual scale limits, which can be set through a webpage's viewport
     72     // meta-tag.
     73     private float mMaxZoomScale;
     74     private float mMinZoomScale;
     75 
     76     // Locks the minimum ZoomScale to the value currently set in mMinZoomScale.
     77     private boolean mMinZoomScaleFixed = true;
     78 
     79     /*
     80      * When loading a new page the WebView does not initially know the final
     81      * width of the page. Therefore, when a new page is loaded in overview mode
     82      * the overview scale is initialized to a default value. This flag is then
     83      * set and used to notify the ZoomManager to take the width of the next
     84      * picture from webkit and use that width to enter into zoom overview mode.
     85      */
     86     private boolean mInitialZoomOverview = false;
     87 
     88     /*
     89      * When in the zoom overview mode, the page's width is fully fit to the
     90      * current window. Additionally while the page is in this state it is
     91      * active, in other words, you can click to follow the links. We cache a
     92      * boolean to enable us to quickly check whether or not we are in overview
     93      * mode, but this value should only be modified by changes to the zoom
     94      * scale.
     95      */
     96     private boolean mInZoomOverview = false;
     97     private int mZoomOverviewWidth;
     98     private float mInvZoomOverviewWidth;
     99 
    100     /*
    101      * These variables track the center point of the zoom and they are used to
    102      * determine the point around which we should zoom. They are stored in view
    103      * coordinates.
    104      */
    105     private float mZoomCenterX;
    106     private float mZoomCenterY;
    107 
    108     /*
    109      * Similar to mZoomCenterX(Y), these track the focus point of the scale
    110      * gesture. The difference is these get updated every time when onScale is
    111      * invoked no matter if a zooming really happens.
    112      */
    113     private float mFocusX;
    114     private float mFocusY;
    115 
    116     /*
    117      * mFocusMovementQueue keeps track of the previous focus point movement
    118      * has been through. Comparing to the difference of the gesture's previous
    119      * span and current span, it determines if the gesture is for panning or
    120      * zooming or both.
    121      */
    122     private FocusMovementQueue mFocusMovementQueue;
    123 
    124     /*
    125      * These values represent the point around which the screen should be
    126      * centered after zooming. In other words it is used to determine the center
    127      * point of the visible document after the page has finished zooming. This
    128      * is important because the zoom may have potentially reflowed the text and
    129      * we need to ensure the proper portion of the document remains on the
    130      * screen.
    131      */
    132     private int mAnchorX;
    133     private int mAnchorY;
    134 
    135     // The scale factor that is used to determine the column width for text
    136     private float mTextWrapScale;
    137 
    138     /*
    139      * The default zoom scale is the scale factor used when the user triggers a
    140      * zoom in by double tapping on the WebView. The value is initially set
    141      * based on the display density, but can be changed at any time via the
    142      * WebSettings.
    143      */
    144     private float mDefaultScale;
    145     private float mInvDefaultScale;
    146 
    147     /*
    148      * The logical density of the display. This is a scaling factor for the
    149      * Density Independent Pixel unit, where one DIP is one pixel on an
    150      * approximately 160 dpi screen (see android.util.DisplayMetrics.density)
    151      */
    152     private float mDisplayDensity;
    153 
    154     /*
    155      * The scale factor that is used as the minimum increment when going from
    156      * overview to reading level on a double tap.
    157      */
    158     private static float MIN_DOUBLE_TAP_SCALE_INCREMENT = 0.5f;
    159 
    160     // the current computed zoom scale and its inverse.
    161     private float mActualScale;
    162     private float mInvActualScale;
    163 
    164     /*
    165      * The initial scale for the WebView. 0 means default. If initial scale is
    166      * greater than 0 the WebView starts with this value as its initial scale. The
    167      * value is converted from an integer percentage so it is guarenteed to have
    168      * no more than 2 significant digits after the decimal.  This restriction
    169      * allows us to convert the scale back to the original percentage by simply
    170      * multiplying the value by 100.
    171      */
    172     private float mInitialScale;
    173 
    174     private static float MINIMUM_SCALE_INCREMENT = 0.007f;
    175 
    176     /*
    177      *  The touch points could be changed even the fingers stop moving.
    178      *  We use the following to filter out the zooming jitters.
    179      */
    180     private static float MINIMUM_SCALE_WITHOUT_JITTER = 0.007f;
    181 
    182     /*
    183      * The following member variables are only to be used for animating zoom. If
    184      * mZoomScale is non-zero then we are in the middle of a zoom animation. The
    185      * other variables are used as a cache (e.g. inverse) or as a way to store
    186      * the state of the view prior to animating (e.g. initial scroll coords).
    187      */
    188     private float mZoomScale;
    189     private float mInvInitialZoomScale;
    190     private float mInvFinalZoomScale;
    191     private int mInitialScrollX;
    192     private int mInitialScrollY;
    193     private long mZoomStart;
    194 
    195     private static final int ZOOM_ANIMATION_LENGTH = 175;
    196 
    197     // whether support multi-touch
    198     private boolean mSupportMultiTouch;
    199 
    200     /**
    201      * True if we have a touch panel capable of detecting smooth pan/scale at the same time
    202      */
    203     private boolean mAllowPanAndScale;
    204 
    205     // use the framework's ScaleGestureDetector to handle multi-touch
    206     private ScaleGestureDetector mScaleDetector;
    207     private boolean mPinchToZoomAnimating = false;
    208 
    209     private boolean mHardwareAccelerated = false;
    210     private boolean mInHWAcceleratedZoom = false;
    211 
    212     public ZoomManager(WebView webView, CallbackProxy callbackProxy) {
    213         mWebView = webView;
    214         mCallbackProxy = callbackProxy;
    215 
    216         /*
    217          * Ideally mZoomOverviewWidth should be mContentWidth. But sites like
    218          * ESPN and Engadget always have wider mContentWidth no matter what the
    219          * viewport size is.
    220          */
    221         setZoomOverviewWidth(WebView.DEFAULT_VIEWPORT_WIDTH);
    222 
    223         mFocusMovementQueue = new FocusMovementQueue();
    224     }
    225 
    226     /**
    227      * Initialize both the default and actual zoom scale to the given density.
    228      *
    229      * @param density The logical density of the display. This is a scaling factor
    230      * for the Density Independent Pixel unit, where one DIP is one pixel on an
    231      * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
    232      */
    233     public void init(float density) {
    234         assert density > 0;
    235 
    236         mDisplayDensity = density;
    237         setDefaultZoomScale(density);
    238         mActualScale = density;
    239         mInvActualScale = 1 / density;
    240         mTextWrapScale = getReadingLevelScale();
    241     }
    242 
    243     /**
    244      * Update the default zoom scale using the given density. It will also reset
    245      * the current min and max zoom scales to the default boundaries as well as
    246      * ensure that the actual scale falls within those boundaries.
    247      *
    248      * @param density The logical density of the display. This is a scaling factor
    249      * for the Density Independent Pixel unit, where one DIP is one pixel on an
    250      * approximately 160 dpi screen (see android.util.DisplayMetrics.density).
    251      */
    252     public void updateDefaultZoomDensity(float density) {
    253         assert density > 0;
    254 
    255         if (Math.abs(density - mDefaultScale) > MINIMUM_SCALE_INCREMENT) {
    256             // Remember the current zoom density before it gets changed.
    257             final float originalDefault = mDefaultScale;
    258             // set the new default density
    259             setDefaultZoomScale(density);
    260             float scaleChange = (originalDefault > 0.0) ? density / originalDefault: 1.0f;
    261             // adjust the scale if it falls outside the new zoom bounds
    262             setZoomScale(mActualScale * scaleChange, true);
    263         }
    264     }
    265 
    266     private void setDefaultZoomScale(float defaultScale) {
    267         final float originalDefault = mDefaultScale;
    268         mDefaultScale = defaultScale;
    269         mInvDefaultScale = 1 / defaultScale;
    270         mDefaultMaxZoomScale = defaultScale * DEFAULT_MAX_ZOOM_SCALE_FACTOR;
    271         mDefaultMinZoomScale = defaultScale * DEFAULT_MIN_ZOOM_SCALE_FACTOR;
    272         if (originalDefault > 0.0 && mMaxZoomScale > 0.0) {
    273             // Keeps max zoom scale when zoom density changes.
    274             mMaxZoomScale = defaultScale / originalDefault * mMaxZoomScale;
    275         } else {
    276             mMaxZoomScale = mDefaultMaxZoomScale;
    277         }
    278         if (originalDefault > 0.0 && mMinZoomScale > 0.0) {
    279             // Keeps min zoom scale when zoom density changes.
    280             mMinZoomScale = defaultScale / originalDefault * mMinZoomScale;
    281         } else {
    282             mMinZoomScale = mDefaultMinZoomScale;
    283         }
    284         if (!exceedsMinScaleIncrement(mMinZoomScale, mMaxZoomScale)) {
    285             mMaxZoomScale = mMinZoomScale;
    286         }
    287     }
    288 
    289     public final float getScale() {
    290         return mActualScale;
    291     }
    292 
    293     public final float getInvScale() {
    294         return mInvActualScale;
    295     }
    296 
    297     public final float getTextWrapScale() {
    298         return mTextWrapScale;
    299     }
    300 
    301     public final float getMaxZoomScale() {
    302         return mMaxZoomScale;
    303     }
    304 
    305     public final float getMinZoomScale() {
    306         return mMinZoomScale;
    307     }
    308 
    309     public final float getDefaultScale() {
    310         return mInitialScale > 0 ? mInitialScale : mDefaultScale;
    311     }
    312 
    313     /**
    314      * Returns the zoom scale used for reading text on a double-tap.
    315      */
    316     public final float getReadingLevelScale() {
    317         WebSettings settings = mWebView.getSettings();
    318         final float doubleTapZoomFactor = settings != null
    319             ? settings.getDoubleTapZoom() / 100.f : 1.0f;
    320         return mDisplayDensity * doubleTapZoomFactor;
    321     }
    322 
    323     public final float getInvDefaultScale() {
    324         return mInvDefaultScale;
    325     }
    326 
    327     public final float getDefaultMaxZoomScale() {
    328         return mDefaultMaxZoomScale;
    329     }
    330 
    331     public final float getDefaultMinZoomScale() {
    332         return mDefaultMinZoomScale;
    333     }
    334 
    335     public final int getDocumentAnchorX() {
    336         return mAnchorX;
    337     }
    338 
    339     public final int getDocumentAnchorY() {
    340         return mAnchorY;
    341     }
    342 
    343     public final void clearDocumentAnchor() {
    344         mAnchorX = mAnchorY = 0;
    345     }
    346 
    347     public final void setZoomCenter(float x, float y) {
    348         mZoomCenterX = x;
    349         mZoomCenterY = y;
    350     }
    351 
    352     public final void setInitialScaleInPercent(int scaleInPercent) {
    353         mInitialScale = scaleInPercent * 0.01f;
    354         mActualScale = mInitialScale > 0 ? mInitialScale : mDefaultScale;
    355         mInvActualScale = 1 / mActualScale;
    356     }
    357 
    358     public final float computeScaleWithLimits(float scale) {
    359         if (scale < mMinZoomScale) {
    360             scale = mMinZoomScale;
    361         } else if (scale > mMaxZoomScale) {
    362             scale = mMaxZoomScale;
    363         }
    364         return scale;
    365     }
    366 
    367     public final boolean isScaleOverLimits(float scale) {
    368         return scale <= mMinZoomScale || scale >= mMaxZoomScale;
    369     }
    370 
    371     public final boolean isZoomScaleFixed() {
    372         return mMinZoomScale >= mMaxZoomScale;
    373     }
    374 
    375     public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) {
    376         return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT;
    377     }
    378 
    379     public boolean willScaleTriggerZoom(float scale) {
    380         return exceedsMinScaleIncrement(scale, mActualScale);
    381     }
    382 
    383     public final boolean canZoomIn() {
    384         return mMaxZoomScale - mActualScale > MINIMUM_SCALE_INCREMENT;
    385     }
    386 
    387     public final boolean canZoomOut() {
    388         return mActualScale - mMinZoomScale > MINIMUM_SCALE_INCREMENT;
    389     }
    390 
    391     public boolean zoomIn() {
    392         return zoom(1.25f);
    393     }
    394 
    395     public boolean zoomOut() {
    396         return zoom(0.8f);
    397     }
    398 
    399     // returns TRUE if zoom out succeeds and FALSE if no zoom changes.
    400     private boolean zoom(float zoomMultiplier) {
    401         mInitialZoomOverview = false;
    402         // TODO: alternatively we can disallow this during draw history mode
    403         mWebView.switchOutDrawHistory();
    404         // Center zooming to the center of the screen.
    405         mZoomCenterX = mWebView.getViewWidth() * .5f;
    406         mZoomCenterY = mWebView.getViewHeight() * .5f;
    407         mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
    408         mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
    409         return startZoomAnimation(mActualScale * zoomMultiplier,
    410             !mWebView.getSettings().getUseFixedViewport());
    411     }
    412 
    413     /**
    414      * Initiates an animated zoom of the WebView.
    415      *
    416      * @return true if the new scale triggered an animation and false otherwise.
    417      */
    418     public boolean startZoomAnimation(float scale, boolean reflowText) {
    419         mInitialZoomOverview = false;
    420         float oldScale = mActualScale;
    421         mInitialScrollX = mWebView.getScrollX();
    422         mInitialScrollY = mWebView.getScrollY();
    423 
    424         // snap to reading level scale if it is close
    425         if (!exceedsMinScaleIncrement(scale, getReadingLevelScale())) {
    426             scale = getReadingLevelScale();
    427         }
    428 
    429         if (mHardwareAccelerated) {
    430             mInHWAcceleratedZoom = true;
    431         }
    432 
    433         setZoomScale(scale, reflowText);
    434 
    435         if (oldScale != mActualScale) {
    436             // use mZoomPickerScale to see zoom preview first
    437             mZoomStart = SystemClock.uptimeMillis();
    438             mInvInitialZoomScale = 1.0f / oldScale;
    439             mInvFinalZoomScale = 1.0f / mActualScale;
    440             mZoomScale = mActualScale;
    441             mWebView.onFixedLengthZoomAnimationStart();
    442             mWebView.invalidate();
    443             return true;
    444         } else {
    445             return false;
    446         }
    447     }
    448 
    449     /**
    450      * This method is called by the WebView's drawing code when a fixed length zoom
    451      * animation is occurring. Its purpose is to animate the zooming of the canvas
    452      * to the desired scale which was specified in startZoomAnimation(...).
    453      *
    454      * A fixed length animation begins when startZoomAnimation(...) is called and
    455      * continues until the ZOOM_ANIMATION_LENGTH time has elapsed. During that
    456      * interval each time the WebView draws it calls this function which is
    457      * responsible for generating the animation.
    458      *
    459      * Additionally, the WebView can check to see if such an animation is currently
    460      * in progress by calling isFixedLengthAnimationInProgress().
    461      */
    462     public void animateZoom(Canvas canvas) {
    463         mInitialZoomOverview = false;
    464         if (mZoomScale == 0) {
    465             Log.w(LOGTAG, "A WebView is attempting to perform a fixed length "
    466                     + "zoom animation when no zoom is in progress");
    467             return;
    468         }
    469 
    470         float zoomScale;
    471         int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
    472         if (interval < ZOOM_ANIMATION_LENGTH) {
    473             float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
    474             zoomScale = 1.0f / (mInvInitialZoomScale
    475                     + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
    476             mWebView.invalidate();
    477         } else {
    478             zoomScale = mZoomScale;
    479             // set mZoomScale to be 0 as we have finished animating
    480             mZoomScale = 0;
    481             mWebView.onFixedLengthZoomAnimationEnd();
    482         }
    483         // calculate the intermediate scroll position. Since we need to use
    484         // zoomScale, we can't use the WebView's pinLocX/Y functions directly.
    485         float scale = zoomScale * mInvInitialZoomScale;
    486         int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) - mZoomCenterX);
    487         tx = -WebView.pinLoc(tx, mWebView.getViewWidth(), Math.round(mWebView.getContentWidth()
    488                 * zoomScale)) + mWebView.getScrollX();
    489         int titleHeight = mWebView.getTitleHeight();
    490         int ty = Math.round(scale
    491                 * (mInitialScrollY + mZoomCenterY - titleHeight)
    492                 - (mZoomCenterY - titleHeight));
    493         ty = -(ty <= titleHeight ? Math.max(ty, 0) : WebView.pinLoc(ty
    494                 - titleHeight, mWebView.getViewHeight(), Math.round(mWebView.getContentHeight()
    495                 * zoomScale)) + titleHeight) + mWebView.getScrollY();
    496 
    497         if (mHardwareAccelerated) {
    498             mWebView.updateScrollCoordinates(mWebView.getScrollX() - tx, mWebView.getScrollY() - ty);
    499             setZoomScale(zoomScale, false);
    500 
    501             if (mZoomScale == 0) {
    502                 // We've reached the end of the zoom animation.
    503                 mInHWAcceleratedZoom = false;
    504             }
    505         } else {
    506             canvas.translate(tx, ty);
    507             canvas.scale(zoomScale, zoomScale);
    508         }
    509     }
    510 
    511     public boolean isZoomAnimating() {
    512         return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating;
    513     }
    514 
    515     public boolean isFixedLengthAnimationInProgress() {
    516         return mZoomScale != 0 || mInHWAcceleratedZoom;
    517     }
    518 
    519     public void updateDoubleTapZoom() {
    520         if (mInZoomOverview) {
    521             mTextWrapScale = getReadingLevelScale();
    522             refreshZoomScale(true);
    523         }
    524     }
    525 
    526     public void refreshZoomScale(boolean reflowText) {
    527         setZoomScale(mActualScale, reflowText, true);
    528     }
    529 
    530     public void setZoomScale(float scale, boolean reflowText) {
    531         setZoomScale(scale, reflowText, false);
    532     }
    533 
    534     private void setZoomScale(float scale, boolean reflowText, boolean force) {
    535         final boolean isScaleLessThanMinZoom = scale < mMinZoomScale;
    536         scale = computeScaleWithLimits(scale);
    537 
    538         // determine whether or not we are in the zoom overview mode
    539         if (isScaleLessThanMinZoom && mMinZoomScale < mDefaultScale) {
    540             mInZoomOverview = true;
    541         } else {
    542             mInZoomOverview = !exceedsMinScaleIncrement(scale, getZoomOverviewScale());
    543         }
    544 
    545         if (reflowText && !mWebView.getSettings().getUseFixedViewport()) {
    546             mTextWrapScale = scale;
    547         }
    548 
    549         if (scale != mActualScale || force) {
    550             float oldScale = mActualScale;
    551             float oldInvScale = mInvActualScale;
    552 
    553             if (scale != mActualScale && !mPinchToZoomAnimating) {
    554                 mCallbackProxy.onScaleChanged(mActualScale, scale);
    555             }
    556 
    557             mActualScale = scale;
    558             mInvActualScale = 1 / scale;
    559 
    560             if (!mWebView.drawHistory() && !mInHWAcceleratedZoom) {
    561 
    562                 // If history Picture is drawn, don't update scroll. They will
    563                 // be updated when we get out of that mode.
    564                 // update our scroll so we don't appear to jump
    565                 // i.e. keep the center of the doc in the center of the view
    566                 // If this is part of a zoom on a HW accelerated canvas, we
    567                 // have already updated the scroll so don't do it again.
    568                 int oldX = mWebView.getScrollX();
    569                 int oldY = mWebView.getScrollY();
    570                 float ratio = scale * oldInvScale;
    571                 float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
    572                 float sy = ratio * oldY + (ratio - 1)
    573                         * (mZoomCenterY - mWebView.getTitleHeight());
    574 
    575                 // Scale all the child views
    576                 mWebView.mViewManager.scaleAll();
    577 
    578                 // as we don't have animation for scaling, don't do animation
    579                 // for scrolling, as it causes weird intermediate state
    580                 int scrollX = mWebView.pinLocX(Math.round(sx));
    581                 int scrollY = mWebView.pinLocY(Math.round(sy));
    582                 if(!mWebView.updateScrollCoordinates(scrollX, scrollY)) {
    583                     // the scroll position is adjusted at the beginning of the
    584                     // zoom animation. But we want to update the WebKit at the
    585                     // end of the zoom animation. See comments in onScaleEnd().
    586                     mWebView.sendOurVisibleRect();
    587                 }
    588             }
    589 
    590             // if the we need to reflow the text then force the VIEW_SIZE_CHANGED
    591             // event to be sent to WebKit
    592             mWebView.sendViewSizeZoom(reflowText);
    593         }
    594     }
    595 
    596     public boolean isDoubleTapEnabled() {
    597         WebSettings settings = mWebView.getSettings();
    598         return settings != null && settings.getUseWideViewPort();
    599     }
    600 
    601     /**
    602      * The double tap gesture can result in different behaviors depending on the
    603      * content that is tapped.
    604      *
    605      * (1) PLUGINS: If the taps occur on a plugin then we maximize the plugin on
    606      * the screen. If the plugin is already maximized then zoom the user into
    607      * overview mode.
    608      *
    609      * (2) HTML/OTHER: If the taps occur outside a plugin then the following
    610      * heuristic is used.
    611      *   A. If the current text wrap scale differs from newly calculated and the
    612      *      layout algorithm specifies the use of NARROW_COLUMNS, then fit to
    613      *      column by reflowing the text.
    614      *   B. If the page is not in overview mode then change to overview mode.
    615      *   C. If the page is in overmode then change to the default scale.
    616      */
    617     public void handleDoubleTap(float lastTouchX, float lastTouchY) {
    618         // User takes action, set initial zoom overview to false.
    619         mInitialZoomOverview = false;
    620         WebSettings settings = mWebView.getSettings();
    621         if (!isDoubleTapEnabled()) {
    622             return;
    623         }
    624 
    625         setZoomCenter(lastTouchX, lastTouchY);
    626         mAnchorX = mWebView.viewToContentX((int) lastTouchX + mWebView.getScrollX());
    627         mAnchorY = mWebView.viewToContentY((int) lastTouchY + mWebView.getScrollY());
    628         settings.setDoubleTapToastCount(0);
    629 
    630         // remove the zoom control after double tap
    631         dismissZoomPicker();
    632 
    633         /*
    634          * If the double tap was on a plugin then either zoom to maximize the
    635          * plugin on the screen or scale to overview mode.
    636          */
    637         Rect pluginBounds = mWebView.getPluginBounds(mAnchorX, mAnchorY);
    638         if (pluginBounds != null) {
    639             if (mWebView.isRectFitOnScreen(pluginBounds)) {
    640                 zoomToOverview();
    641             } else {
    642                 mWebView.centerFitRect(pluginBounds);
    643             }
    644             return;
    645         }
    646 
    647         final float newTextWrapScale;
    648         if (settings.getUseFixedViewport()) {
    649             newTextWrapScale = Math.max(mActualScale, getReadingLevelScale());
    650         } else {
    651             newTextWrapScale = mActualScale;
    652         }
    653         final boolean firstTimeReflow = !exceedsMinScaleIncrement(mActualScale, mTextWrapScale);
    654         if (firstTimeReflow || mInZoomOverview) {
    655             // In case first time reflow or in zoom overview mode, let reflow and zoom
    656             // happen at the same time.
    657             mTextWrapScale = newTextWrapScale;
    658         }
    659         if (settings.isNarrowColumnLayout()
    660                 && exceedsMinScaleIncrement(mTextWrapScale, newTextWrapScale)
    661                 && !firstTimeReflow
    662                 && !mInZoomOverview) {
    663             // Reflow only.
    664             mTextWrapScale = newTextWrapScale;
    665             refreshZoomScale(true);
    666         } else if (!mInZoomOverview && willScaleTriggerZoom(getZoomOverviewScale())) {
    667             // Reflow, if necessary.
    668             if (mTextWrapScale > getReadingLevelScale()) {
    669                 mTextWrapScale = getReadingLevelScale();
    670                 refreshZoomScale(true);
    671             }
    672             zoomToOverview();
    673         } else {
    674             zoomToReadingLevelOrMore();
    675         }
    676     }
    677 
    678     private void setZoomOverviewWidth(int width) {
    679         if (width == 0) {
    680             mZoomOverviewWidth = WebView.DEFAULT_VIEWPORT_WIDTH;
    681         } else {
    682             mZoomOverviewWidth = width;
    683         }
    684         mInvZoomOverviewWidth = 1.0f / width;
    685     }
    686 
    687     /* package */ float getZoomOverviewScale() {
    688         return mWebView.getViewWidth() * mInvZoomOverviewWidth;
    689     }
    690 
    691     public boolean isInZoomOverview() {
    692         return mInZoomOverview;
    693     }
    694 
    695     private void zoomToOverview() {
    696         // Force the titlebar fully reveal in overview mode
    697         int scrollY = mWebView.getScrollY();
    698         if (scrollY < mWebView.getTitleHeight()) {
    699             mWebView.updateScrollCoordinates(mWebView.getScrollX(), 0);
    700         }
    701         startZoomAnimation(getZoomOverviewScale(),
    702             !mWebView.getSettings().getUseFixedViewport());
    703     }
    704 
    705     private void zoomToReadingLevelOrMore() {
    706         final float zoomScale = Math.max(getReadingLevelScale(),
    707                 mActualScale + MIN_DOUBLE_TAP_SCALE_INCREMENT);
    708 
    709         int left = mWebView.nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale);
    710         if (left != WebView.NO_LEFTEDGE) {
    711             // add a 5pt padding to the left edge.
    712             int viewLeft = mWebView.contentToViewX(left < 5 ? 0 : (left - 5))
    713                     - mWebView.getScrollX();
    714             // Re-calculate the zoom center so that the new scroll x will be
    715             // on the left edge.
    716             if (viewLeft > 0) {
    717                 mZoomCenterX = viewLeft * zoomScale / (zoomScale - mActualScale);
    718             } else {
    719                 mWebView.scrollBy(viewLeft, 0);
    720                 mZoomCenterX = 0;
    721             }
    722         }
    723         startZoomAnimation(zoomScale,
    724             !mWebView.getSettings().getUseFixedViewport());
    725     }
    726 
    727     public void updateMultiTouchSupport(Context context) {
    728         // check the preconditions
    729         assert mWebView.getSettings() != null;
    730 
    731         final WebSettings settings = mWebView.getSettings();
    732         final PackageManager pm = context.getPackageManager();
    733         mSupportMultiTouch =
    734                 (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH)
    735                  || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT))
    736                 && settings.supportZoom() && settings.getBuiltInZoomControls();
    737         mAllowPanAndScale =
    738                 pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)
    739                 || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT);
    740 
    741         if (mSupportMultiTouch && (mScaleDetector == null)) {
    742             mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener());
    743         } else if (!mSupportMultiTouch && (mScaleDetector != null)) {
    744             mScaleDetector = null;
    745         }
    746     }
    747 
    748     public boolean supportsMultiTouchZoom() {
    749         return mSupportMultiTouch;
    750     }
    751 
    752     public boolean supportsPanDuringZoom() {
    753         return mAllowPanAndScale;
    754     }
    755 
    756     /**
    757      * Notifies the caller that the ZoomManager is requesting that scale related
    758      * updates should not be sent to webkit. This can occur in cases where the
    759      * ZoomManager is performing an animation and does not want webkit to update
    760      * until the animation is complete.
    761      *
    762      * @return true if scale related updates should not be sent to webkit and
    763      *         false otherwise.
    764      */
    765     public boolean isPreventingWebkitUpdates() {
    766         // currently only animating a multi-touch zoom and fixed length
    767         // animations prevent updates, but others can add their own conditions
    768         // to this method if necessary.
    769         return isZoomAnimating();
    770     }
    771 
    772     public ScaleGestureDetector getMultiTouchGestureDetector() {
    773         return mScaleDetector;
    774     }
    775 
    776     private class FocusMovementQueue {
    777         private static final int QUEUE_CAPACITY = 5;
    778         private float[] mQueue;
    779         private float mSum;
    780         private int mSize;
    781         private int mIndex;
    782 
    783         FocusMovementQueue() {
    784             mQueue = new float[QUEUE_CAPACITY];
    785             mSize = 0;
    786             mSum = 0;
    787             mIndex = 0;
    788         }
    789 
    790         private void clear() {
    791             mSize = 0;
    792             mSum = 0;
    793             mIndex = 0;
    794             for (int i = 0; i < QUEUE_CAPACITY; ++i) {
    795                 mQueue[i] = 0;
    796             }
    797         }
    798 
    799         private void add(float focusDelta) {
    800             mSum += focusDelta;
    801             if (mSize < QUEUE_CAPACITY) {  // fill up the queue.
    802                 mSize++;
    803             } else {  // circulate the queue.
    804                 mSum -= mQueue[mIndex];
    805             }
    806             mQueue[mIndex] = focusDelta;
    807             mIndex = (mIndex + 1) % QUEUE_CAPACITY;
    808         }
    809 
    810         private float getSum() {
    811             return mSum;
    812         }
    813     }
    814 
    815     private class ScaleDetectorListener implements ScaleGestureDetector.OnScaleGestureListener {
    816         private float mAccumulatedSpan;
    817 
    818         public boolean onScaleBegin(ScaleGestureDetector detector) {
    819             mInitialZoomOverview = false;
    820             dismissZoomPicker();
    821             mFocusMovementQueue.clear();
    822             mFocusX = detector.getFocusX();
    823             mFocusY = detector.getFocusY();
    824             mWebView.mViewManager.startZoom();
    825             mWebView.onPinchToZoomAnimationStart();
    826             mAccumulatedSpan = 0;
    827             return true;
    828         }
    829 
    830             // If the user moves the fingers but keeps the same distance between them,
    831             // we should do panning only.
    832         public boolean isPanningOnly(ScaleGestureDetector detector) {
    833             float prevFocusX = mFocusX;
    834             float prevFocusY = mFocusY;
    835             mFocusX = detector.getFocusX();
    836             mFocusY = detector.getFocusY();
    837             float focusDelta = (prevFocusX == 0 && prevFocusY == 0) ? 0 :
    838                     FloatMath.sqrt((mFocusX - prevFocusX) * (mFocusX - prevFocusX)
    839                                    + (mFocusY - prevFocusY) * (mFocusY - prevFocusY));
    840             mFocusMovementQueue.add(focusDelta);
    841             float deltaSpan = detector.getCurrentSpan() - detector.getPreviousSpan() +
    842                     mAccumulatedSpan;
    843             final boolean result = mFocusMovementQueue.getSum() > Math.abs(deltaSpan);
    844             if (result) {
    845                 mAccumulatedSpan += deltaSpan;
    846             } else {
    847                 mAccumulatedSpan = 0;
    848             }
    849             return result;
    850         }
    851 
    852         public boolean handleScale(ScaleGestureDetector detector) {
    853             float scale = detector.getScaleFactor() * mActualScale;
    854 
    855             // if scale is limited by any reason, don't zoom but do ask
    856             // the detector to update the event.
    857             boolean isScaleLimited =
    858                     isScaleOverLimits(scale) || scale < getZoomOverviewScale();
    859 
    860             // Prevent scaling beyond overview scale.
    861             scale = Math.max(computeScaleWithLimits(scale), getZoomOverviewScale());
    862 
    863             if (mPinchToZoomAnimating || willScaleTriggerZoom(scale)) {
    864                 mPinchToZoomAnimating = true;
    865                 // limit the scale change per step
    866                 if (scale > mActualScale) {
    867                     scale = Math.min(scale, mActualScale * 1.25f);
    868                 } else {
    869                     scale = Math.max(scale, mActualScale * 0.8f);
    870                 }
    871                 scale = computeScaleWithLimits(scale);
    872                 // if the scale change is too small, regard it as jitter and skip it.
    873                 if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_WITHOUT_JITTER) {
    874                     return isScaleLimited;
    875                 }
    876                 setZoomCenter(detector.getFocusX(), detector.getFocusY());
    877                 setZoomScale(scale, false);
    878                 mWebView.invalidate();
    879                 return true;
    880             }
    881             return isScaleLimited;
    882         }
    883 
    884         public boolean onScale(ScaleGestureDetector detector) {
    885             if (isPanningOnly(detector) || handleScale(detector)) {
    886                 mFocusMovementQueue.clear();
    887                 return true;
    888             }
    889             return false;
    890         }
    891 
    892         public void onScaleEnd(ScaleGestureDetector detector) {
    893             if (mPinchToZoomAnimating) {
    894                 mPinchToZoomAnimating = false;
    895                 mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
    896                 mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
    897                 // don't reflow when zoom in; when zoom out, do reflow if the
    898                 // new scale is almost minimum scale.
    899                 boolean reflowNow = !canZoomOut() || (mActualScale <= 0.8 * mTextWrapScale);
    900                 // force zoom after mPreviewZoomOnly is set to false so that the
    901                 // new view size will be passed to the WebKit
    902                 refreshZoomScale(reflowNow &&
    903                     !mWebView.getSettings().getUseFixedViewport());
    904                 // call invalidate() to draw without zoom filter
    905                 mWebView.invalidate();
    906             }
    907 
    908             mWebView.mViewManager.endZoom();
    909             mWebView.onPinchToZoomAnimationEnd(detector);
    910         }
    911     }
    912 
    913     public void onSizeChanged(int w, int h, int ow, int oh) {
    914         // reset zoom and anchor to the top left corner of the screen
    915         // unless we are already zooming
    916         if (!isFixedLengthAnimationInProgress()) {
    917             int visibleTitleHeight = mWebView.getVisibleTitleHeight();
    918             mZoomCenterX = 0;
    919             mZoomCenterY = visibleTitleHeight;
    920             mAnchorX = mWebView.viewToContentX(mWebView.getScrollX());
    921             mAnchorY = mWebView.viewToContentY(visibleTitleHeight + mWebView.getScrollY());
    922         }
    923 
    924         // update mMinZoomScale if the minimum zoom scale is not fixed
    925         if (!mMinZoomScaleFixed) {
    926             // when change from narrow screen to wide screen, the new viewWidth
    927             // can be wider than the old content width. We limit the minimum
    928             // scale to 1.0f. The proper minimum scale will be calculated when
    929             // the new picture shows up.
    930             mMinZoomScale = Math.min(1.0f, (float) mWebView.getViewWidth()
    931                     / (mWebView.drawHistory() ? mWebView.getHistoryPictureWidth()
    932                             : mZoomOverviewWidth));
    933             // limit the minZoomScale to the initialScale if it is set
    934             if (mInitialScale > 0 && mInitialScale < mMinZoomScale) {
    935                 mMinZoomScale = mInitialScale;
    936             }
    937         }
    938 
    939         dismissZoomPicker();
    940 
    941         // onSizeChanged() is called during WebView layout. And any
    942         // requestLayout() is blocked during layout. As refreshZoomScale() will
    943         // cause its child View to reposition itself through ViewManager's
    944         // scaleAll(), we need to post a Runnable to ensure requestLayout().
    945         // Additionally, only update the text wrap scale if the width changed.
    946         mWebView.post(new PostScale(w != ow &&
    947             !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview, w < ow));
    948     }
    949 
    950     private class PostScale implements Runnable {
    951         final boolean mUpdateTextWrap;
    952         // Remember the zoom overview state right after rotation since
    953         // it could be changed between the time this callback is initiated and
    954         // the time it's actually run.
    955         final boolean mInZoomOverviewBeforeSizeChange;
    956         final boolean mInPortraitMode;
    957 
    958         public PostScale(boolean updateTextWrap,
    959                          boolean inZoomOverview,
    960                          boolean inPortraitMode) {
    961             mUpdateTextWrap = updateTextWrap;
    962             mInZoomOverviewBeforeSizeChange = inZoomOverview;
    963             mInPortraitMode = inPortraitMode;
    964         }
    965 
    966         public void run() {
    967             if (mWebView.getWebViewCore() != null) {
    968                 // we always force, in case our height changed, in which case we
    969                 // still want to send the notification over to webkit.
    970                 // Keep overview mode unchanged when rotating.
    971                 float newScale = mActualScale;
    972                 if (mWebView.getSettings().getUseWideViewPort() &&
    973                     mInPortraitMode &&
    974                     mInZoomOverviewBeforeSizeChange) {
    975                     newScale = getZoomOverviewScale();
    976                 }
    977                 setZoomScale(newScale, mUpdateTextWrap, true);
    978                 // update the zoom buttons as the scale can be changed
    979                 updateZoomPicker();
    980             }
    981         }
    982     }
    983 
    984     public void updateZoomRange(WebViewCore.ViewState viewState,
    985             int viewWidth, int minPrefWidth) {
    986         if (viewState.mMinScale == 0) {
    987             if (viewState.mMobileSite) {
    988                 if (minPrefWidth > Math.max(0, viewWidth)) {
    989                     mMinZoomScale = (float) viewWidth / minPrefWidth;
    990                     mMinZoomScaleFixed = false;
    991                 } else {
    992                     mMinZoomScale = viewState.mDefaultScale;
    993                     mMinZoomScaleFixed = true;
    994                 }
    995             } else {
    996                 mMinZoomScale = mDefaultMinZoomScale;
    997                 mMinZoomScaleFixed = false;
    998             }
    999         } else {
   1000             mMinZoomScale = viewState.mMinScale;
   1001             mMinZoomScaleFixed = true;
   1002         }
   1003         if (viewState.mMaxScale == 0) {
   1004             mMaxZoomScale = mDefaultMaxZoomScale;
   1005         } else {
   1006             mMaxZoomScale = viewState.mMaxScale;
   1007         }
   1008     }
   1009 
   1010     /**
   1011      * Updates zoom values when Webkit produces a new picture. This method
   1012      * should only be called from the UI thread's message handler.
   1013      */
   1014     public void onNewPicture(WebViewCore.DrawData drawData) {
   1015         final int viewWidth = mWebView.getViewWidth();
   1016         final boolean zoomOverviewWidthChanged = setupZoomOverviewWidth(drawData, viewWidth);
   1017         final float newZoomOverviewScale = getZoomOverviewScale();
   1018         WebSettings settings = mWebView.getSettings();
   1019         if (zoomOverviewWidthChanged && settings.isNarrowColumnLayout() &&
   1020             settings.getUseFixedViewport() &&
   1021             (mInitialZoomOverview || mInZoomOverview)) {
   1022             // Keep mobile site's text wrap scale unchanged.  For mobile sites,
   1023             // the text wrap scale is the same as zoom overview scale.
   1024             if (exceedsMinScaleIncrement(mTextWrapScale, mDefaultScale) ||
   1025                     exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale)) {
   1026                 mTextWrapScale = getReadingLevelScale();
   1027             } else {
   1028                 mTextWrapScale = newZoomOverviewScale;
   1029             }
   1030         }
   1031 
   1032         if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
   1033             mMinZoomScale = newZoomOverviewScale;
   1034             mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
   1035         }
   1036         // fit the content width to the current view for the first new picture
   1037         // after first layout.
   1038         boolean scaleHasDiff = exceedsMinScaleIncrement(newZoomOverviewScale, mActualScale);
   1039         // Make sure the actual scale is no less than zoom overview scale.
   1040         boolean scaleLessThanOverview =
   1041                 (newZoomOverviewScale - mActualScale) >= MINIMUM_SCALE_INCREMENT;
   1042         // Make sure mobile sites are correctly handled since mobile site will
   1043         // change content width after rotating.
   1044         boolean mobileSiteInOverview = mInZoomOverview &&
   1045                 !exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale);
   1046         if (!mWebView.drawHistory() &&
   1047             ((scaleLessThanOverview && settings.getUseWideViewPort())||
   1048                 ((mInitialZoomOverview || mobileSiteInOverview) &&
   1049                     scaleHasDiff && zoomOverviewWidthChanged))) {
   1050             mInitialZoomOverview = false;
   1051             setZoomScale(newZoomOverviewScale, !willScaleTriggerZoom(mTextWrapScale) &&
   1052                 !mWebView.getSettings().getUseFixedViewport());
   1053         } else {
   1054             mInZoomOverview = !scaleHasDiff;
   1055         }
   1056         if (drawData.mFirstLayoutForNonStandardLoad && settings.getLoadWithOverviewMode()) {
   1057             // Set mInitialZoomOverview in case this is the first picture for non standard load,
   1058             // so next new picture could be forced into overview mode if it's true.
   1059             mInitialZoomOverview = mInZoomOverview;
   1060         }
   1061     }
   1062 
   1063     /**
   1064      * Set up correct zoom overview width based on different settings.
   1065      *
   1066      * @param drawData webviewcore draw data
   1067      * @param viewWidth current view width
   1068      */
   1069     private boolean setupZoomOverviewWidth(WebViewCore.DrawData drawData, final int viewWidth) {
   1070         WebSettings settings = mWebView.getSettings();
   1071         int newZoomOverviewWidth = mZoomOverviewWidth;
   1072         if (settings.getUseWideViewPort()) {
   1073             if (drawData.mContentSize.x > 0) {
   1074                 // The webkitDraw for layers will not populate contentSize, and it'll be
   1075                 // ignored for zoom overview width update.
   1076                 newZoomOverviewWidth = Math.min(WebView.sMaxViewportWidth,
   1077                     drawData.mContentSize.x);
   1078             }
   1079         } else {
   1080             // If not use wide viewport, use view width as the zoom overview width.
   1081             newZoomOverviewWidth = Math.round(viewWidth / mDefaultScale);
   1082         }
   1083         if (newZoomOverviewWidth != mZoomOverviewWidth) {
   1084             setZoomOverviewWidth(newZoomOverviewWidth);
   1085             return true;
   1086         }
   1087         return false;
   1088     }
   1089 
   1090     /**
   1091      * Updates zoom values after Webkit completes the initial page layout. It
   1092      * is called when visiting a page for the first time as well as when the
   1093      * user navigates back to a page (in which case we may need to restore the
   1094      * zoom levels to the state they were when you left the page). This method
   1095      * should only be called from the UI thread's message handler.
   1096      */
   1097     public void onFirstLayout(WebViewCore.DrawData drawData) {
   1098         // precondition check
   1099         assert drawData != null;
   1100         assert drawData.mViewState != null;
   1101         assert mWebView.getSettings() != null;
   1102 
   1103         WebViewCore.ViewState viewState = drawData.mViewState;
   1104         final Point viewSize = drawData.mViewSize;
   1105         updateZoomRange(viewState, viewSize.x, drawData.mMinPrefWidth);
   1106         setupZoomOverviewWidth(drawData, mWebView.getViewWidth());
   1107         final float overviewScale = getZoomOverviewScale();
   1108         WebSettings settings = mWebView.getSettings();
   1109         if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
   1110             mMinZoomScale = (mInitialScale > 0) ?
   1111                     Math.min(mInitialScale, overviewScale) : overviewScale;
   1112             mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
   1113         }
   1114 
   1115         if (!mWebView.drawHistory()) {
   1116             float scale;
   1117             if (mInitialScale > 0) {
   1118                 scale = mInitialScale;
   1119                 mTextWrapScale = scale;
   1120             } else if (viewState.mViewScale > 0) {
   1121                 mTextWrapScale = viewState.mTextWrapScale;
   1122                 scale = viewState.mViewScale;
   1123             } else {
   1124                 scale = overviewScale;
   1125                 if (!settings.getUseWideViewPort()
   1126                     || !settings.getLoadWithOverviewMode()) {
   1127                     scale = Math.max(mDefaultScale, scale);
   1128                 }
   1129                 if (settings.isNarrowColumnLayout() &&
   1130                     settings.getUseFixedViewport()) {
   1131                     // When first layout, reflow using the reading level scale to avoid
   1132                     // reflow when double tapped.
   1133                     mTextWrapScale = getReadingLevelScale();
   1134                 }
   1135             }
   1136             boolean reflowText = false;
   1137             if (!viewState.mIsRestored) {
   1138                 if (settings.getUseFixedViewport() && mInitialScale == 0) {
   1139                     // Override the scale only in case of fixed viewport.
   1140                     scale = Math.max(scale, overviewScale);
   1141                     mTextWrapScale = Math.max(mTextWrapScale, overviewScale);
   1142                 }
   1143                 reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
   1144             }
   1145             mInitialZoomOverview = settings.getLoadWithOverviewMode() &&
   1146                     !exceedsMinScaleIncrement(scale, overviewScale);
   1147             setZoomScale(scale, reflowText);
   1148 
   1149             // update the zoom buttons as the scale can be changed
   1150             updateZoomPicker();
   1151         }
   1152     }
   1153 
   1154     public void saveZoomState(Bundle b) {
   1155         b.putFloat("scale", mActualScale);
   1156         b.putFloat("textwrapScale", mTextWrapScale);
   1157         b.putBoolean("overview", mInZoomOverview);
   1158     }
   1159 
   1160     public void restoreZoomState(Bundle b) {
   1161         // as getWidth() / getHeight() of the view are not available yet, set up
   1162         // mActualScale, so that when onSizeChanged() is called, the rest will
   1163         // be set correctly
   1164         mActualScale = b.getFloat("scale", 1.0f);
   1165         mInvActualScale = 1 / mActualScale;
   1166         mTextWrapScale = b.getFloat("textwrapScale", mActualScale);
   1167         mInZoomOverview = b.getBoolean("overview");
   1168     }
   1169 
   1170     private ZoomControlBase getCurrentZoomControl() {
   1171         if (mWebView.getSettings() != null && mWebView.getSettings().supportZoom()) {
   1172             if (mWebView.getSettings().getBuiltInZoomControls()) {
   1173                 if ((mEmbeddedZoomControl == null)
   1174                         && mWebView.getSettings().getDisplayZoomControls()) {
   1175                     mEmbeddedZoomControl = new ZoomControlEmbedded(this, mWebView);
   1176                 }
   1177                 return mEmbeddedZoomControl;
   1178             } else {
   1179                 if (mExternalZoomControl == null) {
   1180                     mExternalZoomControl = new ZoomControlExternal(mWebView);
   1181                 }
   1182                 return mExternalZoomControl;
   1183             }
   1184         }
   1185         return null;
   1186     }
   1187 
   1188     public void invokeZoomPicker() {
   1189         ZoomControlBase control = getCurrentZoomControl();
   1190         if (control != null) {
   1191             control.show();
   1192         }
   1193     }
   1194 
   1195     public void dismissZoomPicker() {
   1196         ZoomControlBase control = getCurrentZoomControl();
   1197         if (control != null) {
   1198             control.hide();
   1199         }
   1200     }
   1201 
   1202     public boolean isZoomPickerVisible() {
   1203         ZoomControlBase control = getCurrentZoomControl();
   1204         return (control != null) ? control.isVisible() : false;
   1205     }
   1206 
   1207     public void updateZoomPicker() {
   1208         ZoomControlBase control = getCurrentZoomControl();
   1209         if (control != null) {
   1210             control.update();
   1211         }
   1212     }
   1213 
   1214     /**
   1215      * The embedded zoom control intercepts touch events and automatically stays
   1216      * visible. The external control needs to constantly refresh its internal
   1217      * timer to stay visible.
   1218      */
   1219     public void keepZoomPickerVisible() {
   1220         ZoomControlBase control = getCurrentZoomControl();
   1221         if (control != null && control == mExternalZoomControl) {
   1222             control.show();
   1223         }
   1224     }
   1225 
   1226     public View getExternalZoomPicker() {
   1227         ZoomControlBase control = getCurrentZoomControl();
   1228         if (control != null && control == mExternalZoomControl) {
   1229             return mExternalZoomControl.getControls();
   1230         } else {
   1231             return null;
   1232         }
   1233     }
   1234 
   1235     public void setHardwareAccelerated() {
   1236         mHardwareAccelerated = true;
   1237     }
   1238 
   1239     /**
   1240      * OnPageFinished called by webview when a page is fully loaded.
   1241      */
   1242     /* package*/ void onPageFinished(String url) {
   1243         // Turn off initial zoom overview flag when a page is fully loaded.
   1244         mInitialZoomOverview = false;
   1245     }
   1246 }
   1247