• 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 WebViewClassic 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 scaling gestures
208     private ScaleGestureDetector mScaleDetector;
209     private boolean mPinchToZoomAnimating = false;
210 
211     private boolean mHardwareAccelerated = false;
212     private boolean mInHWAcceleratedZoom = false;
213 
ZoomManager(WebViewClassic webView, CallbackProxy callbackProxy)214     public ZoomManager(WebViewClassic 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(WebViewClassic.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      */
init(float density)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      */
updateDefaultZoomDensity(float density)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             mDisplayDensity = density;
262             setDefaultZoomScale(density);
263             float scaleChange = (originalDefault > 0.0) ? density / originalDefault: 1.0f;
264             // adjust the scale if it falls outside the new zoom bounds
265             setZoomScale(mActualScale * scaleChange, true);
266         }
267     }
268 
setDefaultZoomScale(float defaultScale)269     private void setDefaultZoomScale(float defaultScale) {
270         final float originalDefault = mDefaultScale;
271         mDefaultScale = defaultScale;
272         mInvDefaultScale = 1 / defaultScale;
273         mDefaultMaxZoomScale = defaultScale * DEFAULT_MAX_ZOOM_SCALE_FACTOR;
274         mDefaultMinZoomScale = defaultScale * DEFAULT_MIN_ZOOM_SCALE_FACTOR;
275         if (originalDefault > 0.0 && mMaxZoomScale > 0.0) {
276             // Keeps max zoom scale when zoom density changes.
277             mMaxZoomScale = defaultScale / originalDefault * mMaxZoomScale;
278         } else {
279             mMaxZoomScale = mDefaultMaxZoomScale;
280         }
281         if (originalDefault > 0.0 && mMinZoomScale > 0.0) {
282             // Keeps min zoom scale when zoom density changes.
283             mMinZoomScale = defaultScale / originalDefault * mMinZoomScale;
284         } else {
285             mMinZoomScale = mDefaultMinZoomScale;
286         }
287         if (!exceedsMinScaleIncrement(mMinZoomScale, mMaxZoomScale)) {
288             mMaxZoomScale = mMinZoomScale;
289         }
290         sanitizeMinMaxScales();
291     }
292 
getScale()293     public final float getScale() {
294         return mActualScale;
295     }
296 
getInvScale()297     public final float getInvScale() {
298         return mInvActualScale;
299     }
300 
getTextWrapScale()301     public final float getTextWrapScale() {
302         return mTextWrapScale;
303     }
304 
getMaxZoomScale()305     public final float getMaxZoomScale() {
306         return mMaxZoomScale;
307     }
308 
getMinZoomScale()309     public final float getMinZoomScale() {
310         return mMinZoomScale;
311     }
312 
getDefaultScale()313     public final float getDefaultScale() {
314         return mDefaultScale;
315     }
316 
317     /**
318      * Returns the zoom scale used for reading text on a double-tap.
319      */
getReadingLevelScale()320     public final float getReadingLevelScale() {
321         return computeScaleWithLimits(computeReadingLevelScale(getZoomOverviewScale()));
322     }
323 
computeReadingLevelScale(float scale)324     /* package */ final float computeReadingLevelScale(float scale) {
325         return Math.max(mDisplayDensity * mDoubleTapZoomFactor,
326                 scale + MIN_DOUBLE_TAP_SCALE_INCREMENT);
327     }
328 
getInvDefaultScale()329     public final float getInvDefaultScale() {
330         return mInvDefaultScale;
331     }
332 
getDefaultMaxZoomScale()333     public final float getDefaultMaxZoomScale() {
334         return mDefaultMaxZoomScale;
335     }
336 
getDefaultMinZoomScale()337     public final float getDefaultMinZoomScale() {
338         return mDefaultMinZoomScale;
339     }
340 
getDocumentAnchorX()341     public final int getDocumentAnchorX() {
342         return mAnchorX;
343     }
344 
getDocumentAnchorY()345     public final int getDocumentAnchorY() {
346         return mAnchorY;
347     }
348 
clearDocumentAnchor()349     public final void clearDocumentAnchor() {
350         mAnchorX = mAnchorY = 0;
351     }
352 
setZoomCenter(float x, float y)353     public final void setZoomCenter(float x, float y) {
354         mZoomCenterX = x;
355         mZoomCenterY = y;
356     }
357 
setInitialScaleInPercent(int scaleInPercent)358     public final void setInitialScaleInPercent(int scaleInPercent) {
359         mInitialScale = scaleInPercent * 0.01f;
360     }
361 
computeScaleWithLimits(float scale)362     public final float computeScaleWithLimits(float scale) {
363         if (scale < mMinZoomScale) {
364             scale = mMinZoomScale;
365         } else if (scale > mMaxZoomScale) {
366             scale = mMaxZoomScale;
367         }
368         return scale;
369     }
370 
isScaleOverLimits(float scale)371     public final boolean isScaleOverLimits(float scale) {
372         return scale <= mMinZoomScale || scale >= mMaxZoomScale;
373     }
374 
isZoomScaleFixed()375     public final boolean isZoomScaleFixed() {
376         return mMinZoomScale >= mMaxZoomScale;
377     }
378 
exceedsMinScaleIncrement(float scaleA, float scaleB)379     public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) {
380         return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT;
381     }
382 
willScaleTriggerZoom(float scale)383     public boolean willScaleTriggerZoom(float scale) {
384         return exceedsMinScaleIncrement(scale, mActualScale);
385     }
386 
canZoomIn()387     public final boolean canZoomIn() {
388         return mMaxZoomScale - mActualScale > MINIMUM_SCALE_INCREMENT;
389     }
390 
canZoomOut()391     public final boolean canZoomOut() {
392         return mActualScale - mMinZoomScale > MINIMUM_SCALE_INCREMENT;
393     }
394 
zoomIn()395     public boolean zoomIn() {
396         return zoom(1.25f);
397     }
398 
zoomOut()399     public boolean zoomOut() {
400         return zoom(0.8f);
401     }
402 
403     // returns TRUE if zoom out succeeds and FALSE if no zoom changes.
zoom(float zoomMultiplier)404     private boolean zoom(float zoomMultiplier) {
405         mInitialZoomOverview = false;
406         // TODO: alternatively we can disallow this during draw history mode
407         mWebView.switchOutDrawHistory();
408         // Center zooming to the center of the screen.
409         mZoomCenterX = mWebView.getViewWidth() * .5f;
410         mZoomCenterY = mWebView.getViewHeight() * .5f;
411         mAnchorX = mWebView.viewToContentX((int) mZoomCenterX + mWebView.getScrollX());
412         mAnchorY = mWebView.viewToContentY((int) mZoomCenterY + mWebView.getScrollY());
413         return startZoomAnimation(mActualScale * zoomMultiplier,
414             !mWebView.getSettings().getUseFixedViewport());
415     }
416 
417     /**
418      * Initiates an animated zoom of the WebView.
419      *
420      * @return true if the new scale triggered an animation and false otherwise.
421      */
startZoomAnimation(float scale, boolean reflowText)422     public boolean startZoomAnimation(float scale, boolean reflowText) {
423         mInitialZoomOverview = false;
424         float oldScale = mActualScale;
425         mInitialScrollX = mWebView.getScrollX();
426         mInitialScrollY = mWebView.getScrollY();
427 
428         // snap to reading level scale if it is close
429         if (!exceedsMinScaleIncrement(scale, getReadingLevelScale())) {
430             scale = getReadingLevelScale();
431         }
432 
433         setZoomScale(scale, reflowText);
434 
435         if (oldScale != mActualScale) {
436             if (mHardwareAccelerated) {
437                 mInHWAcceleratedZoom = true;
438             }
439             // use mZoomPickerScale to see zoom preview first
440             mZoomStart = SystemClock.uptimeMillis();
441             mInvInitialZoomScale = 1.0f / oldScale;
442             mInvFinalZoomScale = 1.0f / mActualScale;
443             mZoomScale = mActualScale;
444             mWebView.onFixedLengthZoomAnimationStart();
445             mWebView.invalidate();
446             return true;
447         } else {
448             return false;
449         }
450     }
451 
452     /**
453      * This method is called by the WebView's drawing code when a fixed length zoom
454      * animation is occurring. Its purpose is to animate the zooming of the canvas
455      * to the desired scale which was specified in startZoomAnimation(...).
456      *
457      * A fixed length animation begins when startZoomAnimation(...) is called and
458      * continues until the ZOOM_ANIMATION_LENGTH time has elapsed. During that
459      * interval each time the WebView draws it calls this function which is
460      * responsible for generating the animation.
461      *
462      * Additionally, the WebView can check to see if such an animation is currently
463      * in progress by calling isFixedLengthAnimationInProgress().
464      */
animateZoom(Canvas canvas)465     public void animateZoom(Canvas canvas) {
466         mInitialZoomOverview = false;
467         if (mZoomScale == 0) {
468             Log.w(LOGTAG, "A WebView is attempting to perform a fixed length "
469                     + "zoom animation when no zoom is in progress");
470             // Now that we've logged about it, go ahead and just recover
471             mInHWAcceleratedZoom = false;
472             return;
473         }
474 
475         float zoomScale;
476         int interval = (int) (SystemClock.uptimeMillis() - mZoomStart);
477         if (interval < ZOOM_ANIMATION_LENGTH) {
478             float ratio = (float) interval / ZOOM_ANIMATION_LENGTH;
479             zoomScale = 1.0f / (mInvInitialZoomScale
480                     + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio);
481             mWebView.invalidate();
482         } else {
483             zoomScale = mZoomScale;
484             // set mZoomScale to be 0 as we have finished animating
485             mZoomScale = 0;
486             mWebView.onFixedLengthZoomAnimationEnd();
487         }
488         // calculate the intermediate scroll position. Since we need to use
489         // zoomScale, we can't use the WebView's pinLocX/Y functions directly.
490         float scale = zoomScale * mInvInitialZoomScale;
491         int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) - mZoomCenterX);
492         tx = -WebViewClassic.pinLoc(tx, mWebView.getViewWidth(), Math.round(mWebView.getContentWidth()
493                 * zoomScale)) + mWebView.getScrollX();
494         int titleHeight = mWebView.getTitleHeight();
495         int ty = Math.round(scale
496                 * (mInitialScrollY + mZoomCenterY - titleHeight)
497                 - (mZoomCenterY - titleHeight));
498         ty = -(ty <= titleHeight ? Math.max(ty, 0) : WebViewClassic.pinLoc(ty
499                 - titleHeight, mWebView.getViewHeight(), Math.round(mWebView.getContentHeight()
500                 * zoomScale)) + titleHeight) + mWebView.getScrollY();
501 
502         if (mHardwareAccelerated) {
503             mWebView.updateScrollCoordinates(mWebView.getScrollX() - tx, mWebView.getScrollY() - ty);
504             // By adding webView matrix, we need to offset the canvas a bit
505             // to make the animation smooth.
506             canvas.translate(tx, ty);
507             setZoomScale(zoomScale, false);
508 
509             if (mZoomScale == 0) {
510                 // We've reached the end of the zoom animation.
511                 mInHWAcceleratedZoom = false;
512 
513                 // Ensure that the zoom level is pushed to WebCore. This has not
514                 // yet occurred because we prevent it from happening while
515                 // mInHWAcceleratedZoom is true.
516                 mWebView.sendViewSizeZoom(false);
517             }
518         } else {
519             canvas.translate(tx, ty);
520             canvas.scale(zoomScale, zoomScale);
521         }
522     }
523 
isZoomAnimating()524     public boolean isZoomAnimating() {
525         return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating;
526     }
527 
isFixedLengthAnimationInProgress()528     public boolean isFixedLengthAnimationInProgress() {
529         return mZoomScale != 0 || mInHWAcceleratedZoom;
530     }
531 
updateDoubleTapZoom(int doubleTapZoom)532     public void updateDoubleTapZoom(int doubleTapZoom) {
533         boolean zoomIn = (mTextWrapScale - mActualScale) < .1f;
534         mDoubleTapZoomFactor = doubleTapZoom / 100.0f;
535         mTextWrapScale = getReadingLevelScale();
536         float newScale = zoomIn ? mTextWrapScale
537                 : Math.min(mTextWrapScale, mActualScale);
538         setZoomScale(newScale, true, true);
539     }
540 
541     public void refreshZoomScale(boolean reflowText) {
542         setZoomScale(mActualScale, reflowText, true);
543     }
544 
545     public void setZoomScale(float scale, boolean reflowText) {
546         setZoomScale(scale, reflowText, false);
547     }
548 
549     private void setZoomScale(float scale, boolean reflowText, boolean force) {
550         final boolean isScaleLessThanMinZoom = scale < mMinZoomScale;
551         scale = computeScaleWithLimits(scale);
552 
553         // determine whether or not we are in the zoom overview mode
554         if (isScaleLessThanMinZoom && mMinZoomScale < mDefaultScale) {
555             mInZoomOverview = true;
556         } else {
557             mInZoomOverview = !exceedsMinScaleIncrement(scale, getZoomOverviewScale());
558         }
559 
560         if (reflowText && !mWebView.getSettings().getUseFixedViewport()) {
561             mTextWrapScale = scale;
562         }
563 
564         if (scale != mActualScale || force) {
565             float oldScale = mActualScale;
566             float oldInvScale = mInvActualScale;
567 
568             if (scale != mActualScale && !mPinchToZoomAnimating) {
569                 mCallbackProxy.onScaleChanged(mActualScale, scale);
570             }
571 
572             mActualScale = scale;
573             mInvActualScale = 1 / scale;
574 
575             if (!mWebView.drawHistory() && !mInHWAcceleratedZoom) {
576 
577                 // If history Picture is drawn, don't update scroll. They will
578                 // be updated when we get out of that mode.
579                 // update our scroll so we don't appear to jump
580                 // i.e. keep the center of the doc in the center of the view
581                 // If this is part of a zoom on a HW accelerated canvas, we
582                 // have already updated the scroll so don't do it again.
583                 int oldX = mWebView.getScrollX();
584                 int oldY = mWebView.getScrollY();
585                 float ratio = scale * oldInvScale;
586                 float sx = ratio * oldX + (ratio - 1) * mZoomCenterX;
587                 float sy = ratio * oldY + (ratio - 1)
588                         * (mZoomCenterY - mWebView.getTitleHeight());
589 
590                 // Scale all the child views
591                 mWebView.mViewManager.scaleAll();
592 
593                 // as we don't have animation for scaling, don't do animation
594                 // for scrolling, as it causes weird intermediate state
595                 int scrollX = mWebView.pinLocX(Math.round(sx));
596                 int scrollY = mWebView.pinLocY(Math.round(sy));
597                 if(!mWebView.updateScrollCoordinates(scrollX, scrollY)) {
598                     // the scroll position is adjusted at the beginning of the
599                     // zoom animation. But we want to update the WebKit at the
600                     // end of the zoom animation. See comments in onScaleEnd().
601                     mWebView.sendOurVisibleRect();
602                 }
603             }
604 
605             // if the we need to reflow the text then force the VIEW_SIZE_CHANGED
606             // event to be sent to WebKit
607             mWebView.sendViewSizeZoom(reflowText);
608         }
609     }
610 
611     public boolean isDoubleTapEnabled() {
612         WebSettings settings = mWebView.getSettings();
613         return settings != null && settings.getUseWideViewPort();
614     }
615 
616     /**
617      * The double tap gesture can result in different behaviors depending on the
618      * content that is tapped.
619      *
620      * (1) PLUGINS: If the taps occur on a plugin then we maximize the plugin on
621      * the screen. If the plugin is already maximized then zoom the user into
622      * overview mode.
623      *
624      * (2) HTML/OTHER: If the taps occur outside a plugin then the following
625      * heuristic is used.
626      *   A. If the current text wrap scale differs from newly calculated and the
627      *      layout algorithm specifies the use of NARROW_COLUMNS, then fit to
628      *      column by reflowing the text.
629      *   B. If the page is not in overview mode then change to overview mode.
630      *   C. If the page is in overmode then change to the default scale.
631      */
632     public void handleDoubleTap(float lastTouchX, float lastTouchY) {
633         // User takes action, set initial zoom overview to false.
634         mInitialZoomOverview = false;
635         WebSettingsClassic settings = mWebView.getSettings();
636         if (!isDoubleTapEnabled()) {
637             return;
638         }
639 
640         setZoomCenter(lastTouchX, lastTouchY);
641         mAnchorX = mWebView.viewToContentX((int) lastTouchX + mWebView.getScrollX());
642         mAnchorY = mWebView.viewToContentY((int) lastTouchY + mWebView.getScrollY());
643         settings.setDoubleTapToastCount(0);
644 
645         // remove the zoom control after double tap
646         dismissZoomPicker();
647 
648         final float newTextWrapScale;
649         if (settings.getUseFixedViewport()) {
650             newTextWrapScale = Math.max(mActualScale, getReadingLevelScale());
651         } else {
652             newTextWrapScale = mActualScale;
653         }
654         final boolean firstTimeReflow = !exceedsMinScaleIncrement(mActualScale, mTextWrapScale);
655         if (firstTimeReflow || mInZoomOverview) {
656             // In case first time reflow or in zoom overview mode, let reflow and zoom
657             // happen at the same time.
658             mTextWrapScale = newTextWrapScale;
659         }
660         if (settings.isNarrowColumnLayout()
661                 && exceedsMinScaleIncrement(mTextWrapScale, newTextWrapScale)
662                 && !firstTimeReflow
663                 && !mInZoomOverview) {
664             // Reflow only.
665             mTextWrapScale = newTextWrapScale;
666             refreshZoomScale(true);
667         } else if (!mInZoomOverview && willScaleTriggerZoom(getZoomOverviewScale())) {
668             // Reflow, if necessary.
669             if (mTextWrapScale > getReadingLevelScale()) {
670                 mTextWrapScale = getReadingLevelScale();
671                 refreshZoomScale(true);
672             }
673             zoomToOverview();
674         } else {
675             zoomToReadingLevel();
676         }
677     }
678 
679     private void setZoomOverviewWidth(int width) {
680         if (width == 0) {
681             mZoomOverviewWidth = WebViewClassic.DEFAULT_VIEWPORT_WIDTH;
682         } else {
683             mZoomOverviewWidth = width;
684         }
685         mInvZoomOverviewWidth = 1.0f / width;
686     }
687 
688     /* package */ float getZoomOverviewScale() {
689         return mWebView.getViewWidth() * mInvZoomOverviewWidth;
690     }
691 
692     public boolean isInZoomOverview() {
693         return mInZoomOverview;
694     }
695 
696     private void zoomToOverview() {
697         // Force the titlebar fully reveal in overview mode
698         int scrollY = mWebView.getScrollY();
699         if (scrollY < mWebView.getTitleHeight()) {
700             mWebView.updateScrollCoordinates(mWebView.getScrollX(), 0);
701         }
702         startZoomAnimation(getZoomOverviewScale(),
703             !mWebView.getSettings().getUseFixedViewport());
704     }
705 
706     private void zoomToReadingLevel() {
707         final float readingScale = getReadingLevelScale();
708 
709         int left = mWebView.getBlockLeftEdge(mAnchorX, mAnchorY, readingScale);
710         if (left != WebViewClassic.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 * readingScale / (readingScale - mActualScale);
718             } else {
719                 mWebView.getWebView().scrollBy(viewLeft, 0);
720                 mZoomCenterX = 0;
721             }
722         }
723         startZoomAnimation(readingScale,
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 getScaleGestureDetector() {
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     private void sanitizeMinMaxScales() {
914         if (mMinZoomScale > mMaxZoomScale) {
915             Log.w(LOGTAG, "mMinZoom > mMaxZoom!!! " + mMinZoomScale + " > " + mMaxZoomScale,
916                     new Exception());
917             mMaxZoomScale = mMinZoomScale;
918         }
919     }
920 
921     public void onSizeChanged(int w, int h, int ow, int oh) {
922         // reset zoom and anchor to the top left corner of the screen
923         // unless we are already zooming
924         if (!isFixedLengthAnimationInProgress()) {
925             int visibleTitleHeight = mWebView.getVisibleTitleHeight();
926             mZoomCenterX = 0;
927             mZoomCenterY = visibleTitleHeight;
928             mAnchorX = mWebView.viewToContentX(mWebView.getScrollX());
929             mAnchorY = mWebView.viewToContentY(visibleTitleHeight + mWebView.getScrollY());
930         }
931 
932         // update mMinZoomScale if the minimum zoom scale is not fixed
933         if (!mMinZoomScaleFixed) {
934             // when change from narrow screen to wide screen, the new viewWidth
935             // can be wider than the old content width. We limit the minimum
936             // scale to 1.0f. The proper minimum scale will be calculated when
937             // the new picture shows up.
938             mMinZoomScale = Math.min(1.0f, (float) mWebView.getViewWidth()
939                     / (mWebView.drawHistory() ? mWebView.getHistoryPictureWidth()
940                             : mZoomOverviewWidth));
941             // limit the minZoomScale to the initialScale if it is set
942             if (mInitialScale > 0 && mInitialScale < mMinZoomScale) {
943                 mMinZoomScale = mInitialScale;
944             }
945             sanitizeMinMaxScales();
946         }
947 
948         dismissZoomPicker();
949 
950         // onSizeChanged() is called during WebView layout. And any
951         // requestLayout() is blocked during layout. As refreshZoomScale() will
952         // cause its child View to reposition itself through ViewManager's
953         // scaleAll(), we need to post a Runnable to ensure requestLayout().
954         // Additionally, only update the text wrap scale if the width changed.
955         mWebView.getWebView().post(new PostScale(w != ow &&
956             !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview, w < ow));
957     }
958 
959     private class PostScale implements Runnable {
960         final boolean mUpdateTextWrap;
961         // Remember the zoom overview state right after rotation since
962         // it could be changed between the time this callback is initiated and
963         // the time it's actually run.
964         final boolean mInZoomOverviewBeforeSizeChange;
965         final boolean mInPortraitMode;
966 
967         public PostScale(boolean updateTextWrap,
968                          boolean inZoomOverview,
969                          boolean inPortraitMode) {
970             mUpdateTextWrap = updateTextWrap;
971             mInZoomOverviewBeforeSizeChange = inZoomOverview;
972             mInPortraitMode = inPortraitMode;
973         }
974 
975         public void run() {
976             if (mWebView.getWebViewCore() != null) {
977                 // we always force, in case our height changed, in which case we
978                 // still want to send the notification over to webkit.
979                 // Keep overview mode unchanged when rotating.
980                 float newScale = mActualScale;
981                 if (mWebView.getSettings().getUseWideViewPort() &&
982                     mInPortraitMode &&
983                     mInZoomOverviewBeforeSizeChange) {
984                     newScale = getZoomOverviewScale();
985                 }
986                 setZoomScale(newScale, mUpdateTextWrap, true);
987                 // update the zoom buttons as the scale can be changed
988                 updateZoomPicker();
989             }
990         }
991     }
992 
993     public void updateZoomRange(WebViewCore.ViewState viewState,
994             int viewWidth, int minPrefWidth) {
995         if (viewState.mMinScale == 0) {
996             if (viewState.mMobileSite) {
997                 if (minPrefWidth > Math.max(0, viewWidth)) {
998                     mMinZoomScale = (float) viewWidth / minPrefWidth;
999                     mMinZoomScaleFixed = false;
1000                 } else {
1001                     mMinZoomScale = viewState.mDefaultScale;
1002                     mMinZoomScaleFixed = true;
1003                 }
1004             } else {
1005                 mMinZoomScale = mDefaultMinZoomScale;
1006                 mMinZoomScaleFixed = false;
1007             }
1008         } else {
1009             mMinZoomScale = viewState.mMinScale;
1010             mMinZoomScaleFixed = true;
1011         }
1012         if (viewState.mMaxScale == 0) {
1013             mMaxZoomScale = mDefaultMaxZoomScale;
1014         } else {
1015             mMaxZoomScale = viewState.mMaxScale;
1016         }
1017         sanitizeMinMaxScales();
1018     }
1019 
1020     /**
1021      * Updates zoom values when Webkit produces a new picture. This method
1022      * should only be called from the UI thread's message handler.
1023      *
1024      * @return True if zoom value has changed
1025      */
1026     public boolean onNewPicture(WebViewCore.DrawData drawData) {
1027         final int viewWidth = mWebView.getViewWidth();
1028         final boolean zoomOverviewWidthChanged = setupZoomOverviewWidth(drawData, viewWidth);
1029         final float newZoomOverviewScale = getZoomOverviewScale();
1030         WebSettingsClassic settings = mWebView.getSettings();
1031         if (zoomOverviewWidthChanged && settings.isNarrowColumnLayout() &&
1032             settings.getUseFixedViewport() &&
1033             (mInitialZoomOverview || mInZoomOverview)) {
1034             // Keep mobile site's text wrap scale unchanged.  For mobile sites,
1035             // the text wrap scale is the same as zoom overview scale.
1036             if (exceedsMinScaleIncrement(mTextWrapScale, mDefaultScale) ||
1037                     exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale)) {
1038                 mTextWrapScale = getReadingLevelScale();
1039             } else {
1040                 mTextWrapScale = newZoomOverviewScale;
1041             }
1042         }
1043 
1044         if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
1045             mMinZoomScale = newZoomOverviewScale;
1046             mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
1047             sanitizeMinMaxScales();
1048         }
1049         // fit the content width to the current view for the first new picture
1050         // after first layout.
1051         boolean scaleHasDiff = exceedsMinScaleIncrement(newZoomOverviewScale, mActualScale);
1052         // Make sure the actual scale is no less than zoom overview scale.
1053         boolean scaleLessThanOverview =
1054                 (newZoomOverviewScale - mActualScale) >= MINIMUM_SCALE_INCREMENT;
1055         // Make sure mobile sites are correctly handled since mobile site will
1056         // change content width after rotating.
1057         boolean mobileSiteInOverview = mInZoomOverview &&
1058                 !exceedsMinScaleIncrement(newZoomOverviewScale, mDefaultScale);
1059         if (!mWebView.drawHistory() &&
1060             ((scaleLessThanOverview && settings.getUseWideViewPort())||
1061                 ((mInitialZoomOverview || mobileSiteInOverview) &&
1062                     scaleHasDiff && zoomOverviewWidthChanged))) {
1063             mInitialZoomOverview = false;
1064             setZoomScale(newZoomOverviewScale, !willScaleTriggerZoom(mTextWrapScale) &&
1065                 !mWebView.getSettings().getUseFixedViewport());
1066         } else {
1067             mInZoomOverview = !scaleHasDiff;
1068         }
1069         if (drawData.mFirstLayoutForNonStandardLoad && settings.getLoadWithOverviewMode()) {
1070             // Set mInitialZoomOverview in case this is the first picture for non standard load,
1071             // so next new picture could be forced into overview mode if it's true.
1072             mInitialZoomOverview = mInZoomOverview;
1073         }
1074 
1075         return scaleHasDiff;
1076     }
1077 
1078     /**
1079      * Set up correct zoom overview width based on different settings.
1080      *
1081      * @param drawData webviewcore draw data
1082      * @param viewWidth current view width
1083      */
1084     private boolean setupZoomOverviewWidth(WebViewCore.DrawData drawData, final int viewWidth) {
1085         WebSettings settings = mWebView.getSettings();
1086         int newZoomOverviewWidth = mZoomOverviewWidth;
1087         if (settings.getUseWideViewPort()) {
1088             if (drawData.mContentSize.x > 0) {
1089                 // The webkitDraw for layers will not populate contentSize, and it'll be
1090                 // ignored for zoom overview width update.
1091                 newZoomOverviewWidth = Math.min(WebViewClassic.sMaxViewportWidth,
1092                     drawData.mContentSize.x);
1093             }
1094         } else {
1095             // If not use wide viewport, use view width as the zoom overview width.
1096             newZoomOverviewWidth = Math.round(viewWidth / mDefaultScale);
1097         }
1098         if (newZoomOverviewWidth != mZoomOverviewWidth) {
1099             setZoomOverviewWidth(newZoomOverviewWidth);
1100             return true;
1101         }
1102         return false;
1103     }
1104 
1105     /**
1106      * Updates zoom values after Webkit completes the initial page layout. It
1107      * is called when visiting a page for the first time as well as when the
1108      * user navigates back to a page (in which case we may need to restore the
1109      * zoom levels to the state they were when you left the page). This method
1110      * should only be called from the UI thread's message handler.
1111      */
1112     public void onFirstLayout(WebViewCore.DrawData drawData) {
1113         // precondition check
1114         assert drawData != null;
1115         assert drawData.mViewState != null;
1116         assert mWebView.getSettings() != null;
1117 
1118         WebViewCore.ViewState viewState = drawData.mViewState;
1119         final Point viewSize = drawData.mViewSize;
1120         updateZoomRange(viewState, viewSize.x, drawData.mMinPrefWidth);
1121         setupZoomOverviewWidth(drawData, mWebView.getViewWidth());
1122         final float overviewScale = getZoomOverviewScale();
1123         WebSettingsClassic settings = mWebView.getSettings();
1124         if (!mMinZoomScaleFixed || settings.getUseWideViewPort()) {
1125             mMinZoomScale = (mInitialScale > 0) ?
1126                     Math.min(mInitialScale, overviewScale) : overviewScale;
1127             mMaxZoomScale = Math.max(mMaxZoomScale, mMinZoomScale);
1128             sanitizeMinMaxScales();
1129         }
1130 
1131         if (!mWebView.drawHistory()) {
1132             float scale;
1133             if (mInitialScale > 0) {
1134                 scale = mInitialScale;
1135             } else if (viewState.mIsRestored || viewState.mViewScale > 0) {
1136                 scale = (viewState.mViewScale > 0)
1137                     ? viewState.mViewScale : overviewScale;
1138                 mTextWrapScale = (viewState.mTextWrapScale > 0)
1139                     ? viewState.mTextWrapScale : getReadingLevelScale();
1140             } else {
1141                 scale = overviewScale;
1142                 if (!settings.getUseWideViewPort()
1143                     || !settings.getLoadWithOverviewMode()) {
1144                     scale = Math.max(mDefaultScale, scale);
1145                 }
1146                 if (settings.isNarrowColumnLayout() &&
1147                     settings.getUseFixedViewport()) {
1148                     // When first layout, reflow using the reading level scale to avoid
1149                     // reflow when double tapped.
1150                     mTextWrapScale = getReadingLevelScale();
1151                 }
1152             }
1153             boolean reflowText = false;
1154             if (!viewState.mIsRestored) {
1155                 if (settings.getUseFixedViewport()) {
1156                     // Override the scale only in case of fixed viewport.
1157                     scale = Math.max(scale, overviewScale);
1158                     mTextWrapScale = Math.max(mTextWrapScale, overviewScale);
1159                 }
1160                 reflowText = exceedsMinScaleIncrement(mTextWrapScale, scale);
1161             }
1162             mInitialZoomOverview = settings.getLoadWithOverviewMode() &&
1163                     !exceedsMinScaleIncrement(scale, overviewScale);
1164             setZoomScale(scale, reflowText);
1165 
1166             // update the zoom buttons as the scale can be changed
1167             updateZoomPicker();
1168         }
1169     }
1170 
saveZoomState(Bundle b)1171     public void saveZoomState(Bundle b) {
1172         b.putFloat("scale", mActualScale);
1173         b.putFloat("textwrapScale", mTextWrapScale);
1174         b.putBoolean("overview", mInZoomOverview);
1175     }
1176 
restoreZoomState(Bundle b)1177     public void restoreZoomState(Bundle b) {
1178         // as getWidth() / getHeight() of the view are not available yet, set up
1179         // mActualScale, so that when onSizeChanged() is called, the rest will
1180         // be set correctly
1181         mActualScale = b.getFloat("scale", 1.0f);
1182         mInvActualScale = 1 / mActualScale;
1183         mTextWrapScale = b.getFloat("textwrapScale", mActualScale);
1184         mInZoomOverview = b.getBoolean("overview");
1185     }
1186 
getCurrentZoomControl()1187     private ZoomControlBase getCurrentZoomControl() {
1188         if (mWebView.getSettings() != null && mWebView.getSettings().supportZoom()) {
1189             if (mWebView.getSettings().getBuiltInZoomControls()) {
1190                 if ((mEmbeddedZoomControl == null)
1191                         && mWebView.getSettings().getDisplayZoomControls()) {
1192                     mEmbeddedZoomControl = new ZoomControlEmbedded(this, mWebView);
1193                 }
1194                 return mEmbeddedZoomControl;
1195             } else {
1196                 if (mExternalZoomControl == null) {
1197                     mExternalZoomControl = new ZoomControlExternal(mWebView);
1198                 }
1199                 return mExternalZoomControl;
1200             }
1201         }
1202         return null;
1203     }
1204 
invokeZoomPicker()1205     public void invokeZoomPicker() {
1206         ZoomControlBase control = getCurrentZoomControl();
1207         if (control != null) {
1208             control.show();
1209         }
1210     }
1211 
dismissZoomPicker()1212     public void dismissZoomPicker() {
1213         ZoomControlBase control = getCurrentZoomControl();
1214         if (control != null) {
1215             control.hide();
1216         }
1217     }
1218 
isZoomPickerVisible()1219     public boolean isZoomPickerVisible() {
1220         ZoomControlBase control = getCurrentZoomControl();
1221         return (control != null) ? control.isVisible() : false;
1222     }
1223 
updateZoomPicker()1224     public void updateZoomPicker() {
1225         ZoomControlBase control = getCurrentZoomControl();
1226         if (control != null) {
1227             control.update();
1228         }
1229     }
1230 
1231     /**
1232      * The embedded zoom control intercepts touch events and automatically stays
1233      * visible. The external control needs to constantly refresh its internal
1234      * timer to stay visible.
1235      */
keepZoomPickerVisible()1236     public void keepZoomPickerVisible() {
1237         ZoomControlBase control = getCurrentZoomControl();
1238         if (control != null && control == mExternalZoomControl) {
1239             control.show();
1240         }
1241     }
1242 
getExternalZoomPicker()1243     public View getExternalZoomPicker() {
1244         ZoomControlBase control = getCurrentZoomControl();
1245         if (control != null && control == mExternalZoomControl) {
1246             return mExternalZoomControl.getControls();
1247         } else {
1248             return null;
1249         }
1250     }
1251 
setHardwareAccelerated()1252     public void setHardwareAccelerated() {
1253         mHardwareAccelerated = true;
1254     }
1255 
1256     /**
1257      * OnPageFinished called by webview when a page is fully loaded.
1258      */
onPageFinished(String url)1259     /* package*/ void onPageFinished(String url) {
1260         // Turn off initial zoom overview flag when a page is fully loaded.
1261         mInitialZoomOverview = false;
1262     }
1263 }
1264