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 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 */ 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 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 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 293 public final float getScale() { 294 return mActualScale; 295 } 296 297 public final float getInvScale() { 298 return mInvActualScale; 299 } 300 301 public final float getTextWrapScale() { 302 return mTextWrapScale; 303 } 304 305 public final float getMaxZoomScale() { 306 return mMaxZoomScale; 307 } 308 309 public final float getMinZoomScale() { 310 return mMinZoomScale; 311 } 312 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 */ 320 public final float getReadingLevelScale() { 321 return computeScaleWithLimits(computeReadingLevelScale(getZoomOverviewScale())); 322 } 323 324 /* package */ final float computeReadingLevelScale(float scale) { 325 return Math.max(mDisplayDensity * mDoubleTapZoomFactor, 326 scale + MIN_DOUBLE_TAP_SCALE_INCREMENT); 327 } 328 329 public final float getInvDefaultScale() { 330 return mInvDefaultScale; 331 } 332 333 public final float getDefaultMaxZoomScale() { 334 return mDefaultMaxZoomScale; 335 } 336 337 public final float getDefaultMinZoomScale() { 338 return mDefaultMinZoomScale; 339 } 340 341 public final int getDocumentAnchorX() { 342 return mAnchorX; 343 } 344 345 public final int getDocumentAnchorY() { 346 return mAnchorY; 347 } 348 349 public final void clearDocumentAnchor() { 350 mAnchorX = mAnchorY = 0; 351 } 352 353 public final void setZoomCenter(float x, float y) { 354 mZoomCenterX = x; 355 mZoomCenterY = y; 356 } 357 358 public final void setInitialScaleInPercent(int scaleInPercent) { 359 mInitialScale = scaleInPercent * 0.01f; 360 } 361 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 371 public final boolean isScaleOverLimits(float scale) { 372 return scale <= mMinZoomScale || scale >= mMaxZoomScale; 373 } 374 375 public final boolean isZoomScaleFixed() { 376 return mMinZoomScale >= mMaxZoomScale; 377 } 378 379 public static final boolean exceedsMinScaleIncrement(float scaleA, float scaleB) { 380 return Math.abs(scaleA - scaleB) >= MINIMUM_SCALE_INCREMENT; 381 } 382 383 public boolean willScaleTriggerZoom(float scale) { 384 return exceedsMinScaleIncrement(scale, mActualScale); 385 } 386 387 public final boolean canZoomIn() { 388 return mMaxZoomScale - mActualScale > MINIMUM_SCALE_INCREMENT; 389 } 390 391 public final boolean canZoomOut() { 392 return mActualScale - mMinZoomScale > MINIMUM_SCALE_INCREMENT; 393 } 394 395 public boolean zoomIn() { 396 return zoom(1.25f); 397 } 398 399 public boolean zoomOut() { 400 return zoom(0.8f); 401 } 402 403 // returns TRUE if zoom out succeeds and FALSE if no zoom changes. 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 */ 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 */ 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 524 public boolean isZoomAnimating() { 525 return isFixedLengthAnimationInProgress() || mPinchToZoomAnimating; 526 } 527 528 public boolean isFixedLengthAnimationInProgress() { 529 return mZoomScale != 0 || mInHWAcceleratedZoom; 530 } 531 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 1171 public void saveZoomState(Bundle b) { 1172 b.putFloat("scale", mActualScale); 1173 b.putFloat("textwrapScale", mTextWrapScale); 1174 b.putBoolean("overview", mInZoomOverview); 1175 } 1176 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 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 1205 public void invokeZoomPicker() { 1206 ZoomControlBase control = getCurrentZoomControl(); 1207 if (control != null) { 1208 control.show(); 1209 } 1210 } 1211 1212 public void dismissZoomPicker() { 1213 ZoomControlBase control = getCurrentZoomControl(); 1214 if (control != null) { 1215 control.hide(); 1216 } 1217 } 1218 1219 public boolean isZoomPickerVisible() { 1220 ZoomControlBase control = getCurrentZoomControl(); 1221 return (control != null) ? control.isVisible() : false; 1222 } 1223 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 */ 1236 public void keepZoomPickerVisible() { 1237 ZoomControlBase control = getCurrentZoomControl(); 1238 if (control != null && control == mExternalZoomControl) { 1239 control.show(); 1240 } 1241 } 1242 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 1252 public void setHardwareAccelerated() { 1253 mHardwareAccelerated = true; 1254 } 1255 1256 /** 1257 * OnPageFinished called by webview when a page is fully loaded. 1258 */ 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