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