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