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