• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ZoomManager(WebView webView, CallbackProxy callbackProxy)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      */
init(float density)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      */
updateDefaultZoomDensity(float density)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 
setDefaultZoomScale(float defaultScale)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 
getScale()289     public final float getScale() {
290         return mActualScale;
291     }
292 
getInvScale()293     public final float getInvScale() {
294         return mInvActualScale;
295     }
296 
getTextWrapScale()297     public final float getTextWrapScale() {
298         return mTextWrapScale;
299     }
300 
getMaxZoomScale()301     public final float getMaxZoomScale() {
302         return mMaxZoomScale;
303     }
304 
getMinZoomScale()305     public final float getMinZoomScale() {
306         return mMinZoomScale;
307     }
308 
getDefaultScale()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      */
getReadingLevelScale()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 
getInvDefaultScale()323     public final float getInvDefaultScale() {
324         return mInvDefaultScale;
325     }
326 
getDefaultMaxZoomScale()327     public final float getDefaultMaxZoomScale() {
328         return mDefaultMaxZoomScale;
329     }
330 
getDefaultMinZoomScale()331     public final float getDefaultMinZoomScale() {
332         return mDefaultMinZoomScale;
333     }
334 
getDocumentAnchorX()335     public final int getDocumentAnchorX() {
336         return mAnchorX;
337     }
338 
getDocumentAnchorY()339     public final int getDocumentAnchorY() {
340         return mAnchorY;
341     }
342 
clearDocumentAnchor()343     public final void clearDocumentAnchor() {
344         mAnchorX = mAnchorY = 0;
345     }
346 
setZoomCenter(float x, float y)347     public final void setZoomCenter(float x, float y) {
348         mZoomCenterX = x;
349         mZoomCenterY = y;
350     }
351 
setInitialScaleInPercent(int scaleInPercent)352     public final void setInitialScaleInPercent(int scaleInPercent) {
353         mInitialScale = scaleInPercent * 0.01f;
354         mActualScale = mInitialScale > 0 ? mInitialScale : mDefaultScale;
355         mInvActualScale = 1 / mActualScale;
356     }
357 
computeScaleWithLimits(float scale)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 
isScaleOverLimits(float scale)367     public final boolean isScaleOverLimits(float scale) {
368         return scale <= mMinZoomScale || scale >= mMaxZoomScale;
369     }
370 
isZoomScaleFixed()371     public final boolean isZoomScaleFixed() {
372         return mMinZoomScale >= mMaxZoomScale;
373     }
374 
exceedsMinScaleIncrement(float scaleA, float scaleB)375     public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) {
376         return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT;
377     }
378 
willScaleTriggerZoom(float scale)379     public boolean willScaleTriggerZoom(float scale) {
380         return exceedsMinScaleIncrement(scale, mActualScale);
381     }
382 
canZoomIn()383     public final boolean canZoomIn() {
384         return mMaxZoomScale - mActualScale > MINIMUM_SCALE_INCREMENT;
385     }
386 
canZoomOut()387     public final boolean canZoomOut() {
388         return mActualScale - mMinZoomScale > MINIMUM_SCALE_INCREMENT;
389     }
390 
zoomIn()391     public boolean zoomIn() {
392         return zoom(1.25f);
393     }
394 
zoomOut()395     public boolean zoomOut() {
396         return zoom(0.8f);
397     }
398 
399     // returns TRUE if zoom out succeeds and FALSE if no zoom changes.
zoom(float zoomMultiplier)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      */
startZoomAnimation(float scale, boolean reflowText)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      */
animateZoom(Canvas canvas)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 
isZoomAnimating()511     public boolean isZoomAnimating() {
512         return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating;
513     }
514 
isFixedLengthAnimationInProgress()515     public boolean isFixedLengthAnimationInProgress() {
516         return mZoomScale != 0 || mInHWAcceleratedZoom;
517     }
518 
updateDoubleTapZoom()519     public void updateDoubleTapZoom() {
520         if (mInZoomOverview) {
521             mTextWrapScale = getReadingLevelScale();
522             refreshZoomScale(true);
523         }
524     }
525 
refreshZoomScale(boolean reflowText)526     public void refreshZoomScale(boolean reflowText) {
527         setZoomScale(mActualScale, reflowText, true);
528     }
529 
setZoomScale(float scale, boolean reflowText)530     public void setZoomScale(float scale, boolean reflowText) {
531         setZoomScale(scale, reflowText, false);
532     }
533 
setZoomScale(float scale, boolean reflowText, boolean force)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 
saveZoomState(Bundle b)1154     public void saveZoomState(Bundle b) {
1155         b.putFloat("scale", mActualScale);
1156         b.putFloat("textwrapScale", mTextWrapScale);
1157         b.putBoolean("overview", mInZoomOverview);
1158     }
1159 
restoreZoomState(Bundle b)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 
getCurrentZoomControl()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 
invokeZoomPicker()1188     public void invokeZoomPicker() {
1189         ZoomControlBase control = getCurrentZoomControl();
1190         if (control != null) {
1191             control.show();
1192         }
1193     }
1194 
dismissZoomPicker()1195     public void dismissZoomPicker() {
1196         ZoomControlBase control = getCurrentZoomControl();
1197         if (control != null) {
1198             control.hide();
1199         }
1200     }
1201 
isZoomPickerVisible()1202     public boolean isZoomPickerVisible() {
1203         ZoomControlBase control = getCurrentZoomControl();
1204         return (control != null) ? control.isVisible() : false;
1205     }
1206 
updateZoomPicker()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      */
keepZoomPickerVisible()1219     public void keepZoomPickerVisible() {
1220         ZoomControlBase control = getCurrentZoomControl();
1221         if (control != null && control == mExternalZoomControl) {
1222             control.show();
1223         }
1224     }
1225 
getExternalZoomPicker()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 
setHardwareAccelerated()1235     public void setHardwareAccelerated() {
1236         mHardwareAccelerated = true;
1237     }
1238 
1239     /**
1240      * OnPageFinished called by webview when a page is fully loaded.
1241      */
onPageFinished(String url)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