1 /* 2 * Copyright (C) 2006 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 com.android.internal.R; 20 21 import android.annotation.Widget; 22 import android.app.AlertDialog; 23 import android.content.Context; 24 import android.content.DialogInterface; 25 import android.content.DialogInterface.OnCancelListener; 26 import android.content.Intent; 27 import android.content.pm.PackageManager; 28 import android.content.res.Resources; 29 import android.database.DataSetObserver; 30 import android.graphics.Bitmap; 31 import android.graphics.BitmapFactory; 32 import android.graphics.BitmapShader; 33 import android.graphics.Canvas; 34 import android.graphics.Color; 35 import android.graphics.Interpolator; 36 import android.graphics.Paint; 37 import android.graphics.Picture; 38 import android.graphics.Point; 39 import android.graphics.Rect; 40 import android.graphics.RectF; 41 import android.graphics.Region; 42 import android.graphics.Shader; 43 import android.graphics.drawable.Drawable; 44 import android.net.Uri; 45 import android.net.http.SslCertificate; 46 import android.os.Bundle; 47 import android.os.Handler; 48 import android.os.Message; 49 import android.os.ServiceManager; 50 import android.os.SystemClock; 51 import android.text.IClipboard; 52 import android.text.Selection; 53 import android.text.Spannable; 54 import android.util.AttributeSet; 55 import android.util.EventLog; 56 import android.util.Log; 57 import android.util.TypedValue; 58 import android.view.Gravity; 59 import android.view.KeyEvent; 60 import android.view.LayoutInflater; 61 import android.view.MotionEvent; 62 import android.view.ScaleGestureDetector; 63 import android.view.SoundEffectConstants; 64 import android.view.VelocityTracker; 65 import android.view.View; 66 import android.view.ViewConfiguration; 67 import android.view.ViewGroup; 68 import android.view.ViewTreeObserver; 69 import android.view.animation.AlphaAnimation; 70 import android.view.inputmethod.EditorInfo; 71 import android.view.inputmethod.InputConnection; 72 import android.view.inputmethod.InputMethodManager; 73 import android.webkit.WebTextView.AutoCompleteAdapter; 74 import android.webkit.WebViewCore.EventHub; 75 import android.webkit.WebViewCore.TouchEventData; 76 import android.widget.AbsoluteLayout; 77 import android.widget.Adapter; 78 import android.widget.AdapterView; 79 import android.widget.AdapterView.OnItemClickListener; 80 import android.widget.ArrayAdapter; 81 import android.widget.CheckedTextView; 82 import android.widget.EdgeGlow; 83 import android.widget.FrameLayout; 84 import android.widget.LinearLayout; 85 import android.widget.ListView; 86 import android.widget.OverScroller; 87 import android.widget.Toast; 88 import android.widget.ZoomButtonsController; 89 import android.widget.ZoomControls; 90 91 import java.io.File; 92 import java.io.FileInputStream; 93 import java.io.FileNotFoundException; 94 import java.io.FileOutputStream; 95 import java.net.URLDecoder; 96 import java.util.ArrayList; 97 import java.util.HashMap; 98 import java.util.List; 99 import java.util.Map; 100 import java.util.Set; 101 102 import junit.framework.Assert; 103 104 /** 105 * <p>A View that displays web pages. This class is the basis upon which you 106 * can roll your own web browser or simply display some online content within your Activity. 107 * It uses the WebKit rendering engine to display 108 * web pages and includes methods to navigate forward and backward 109 * through a history, zoom in and out, perform text searches and more.</p> 110 * <p>To enable the built-in zoom, set 111 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} 112 * (introduced in API version 3). 113 * <p>Note that, in order for your Activity to access the Internet and load web pages 114 * in a WebView, you must add the {@code INTERNET} permissions to your 115 * Android Manifest file:</p> 116 * <pre><uses-permission android:name="android.permission.INTERNET" /></pre> 117 * 118 * <p>This must be a child of the <a 119 * href="{@docRoot}guide/topics/manifest/manifest-element.html">{@code <manifest>}</a> 120 * element.</p> 121 * 122 * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-webview.html">Web View 123 * tutorial</a>.</p> 124 * 125 * <h3>Basic usage</h3> 126 * 127 * <p>By default, a WebView provides no browser-like widgets, does not 128 * enable JavaScript and web page errors are ignored. If your goal is only 129 * to display some HTML as a part of your UI, this is probably fine; 130 * the user won't need to interact with the web page beyond reading 131 * it, and the web page won't need to interact with the user. If you 132 * actually want a full-blown web browser, then you probably want to 133 * invoke the Browser application with a URL Intent rather than show it 134 * with a WebView. For example: 135 * <pre> 136 * Uri uri = Uri.parse("http://www.example.com"); 137 * Intent intent = new Intent(Intent.ACTION_VIEW, uri); 138 * startActivity(intent); 139 * </pre> 140 * <p>See {@link android.content.Intent} for more information.</p> 141 * 142 * <p>To provide a WebView in your own Activity, include a {@code <WebView>} in your layout, 143 * or set the entire Activity window as a WebView during {@link 144 * android.app.Activity#onCreate(Bundle) onCreate()}:</p> 145 * <pre class="prettyprint"> 146 * WebView webview = new WebView(this); 147 * setContentView(webview); 148 * </pre> 149 * 150 * <p>Then load the desired web page:</p> 151 * <pre> 152 * // Simplest usage: note that an exception will NOT be thrown 153 * // if there is an error loading this page (see below). 154 * webview.loadUrl("http://slashdot.org/"); 155 * 156 * // OR, you can also load from an HTML string: 157 * String summary = "<html><body>You scored <b>192</b> points.</body></html>"; 158 * webview.loadData(summary, "text/html", "utf-8"); 159 * // ... although note that there are restrictions on what this HTML can do. 160 * // See the JavaDocs for {@link #loadData(String,String,String) loadData()} and {@link 161 * #loadDataWithBaseURL(String,String,String,String,String) loadDataWithBaseURL()} for more info. 162 * </pre> 163 * 164 * <p>A WebView has several customization points where you can add your 165 * own behavior. These are:</p> 166 * 167 * <ul> 168 * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass. 169 * This class is called when something that might impact a 170 * browser UI happens, for instance, progress updates and 171 * JavaScript alerts are sent here (see <a 172 * href="{@docRoot}guide/developing/debug-tasks.html#DebuggingWebPages">Debugging Tasks</a>). 173 * </li> 174 * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. 175 * It will be called when things happen that impact the 176 * rendering of the content, eg, errors or form submissions. You 177 * can also intercept URL loading here (via {@link 178 * android.webkit.WebViewClient#shouldOverrideUrlLoading(WebView,String) 179 * shouldOverrideUrlLoading()}).</li> 180 * <li>Modifying the {@link android.webkit.WebSettings}, such as 181 * enabling JavaScript with {@link android.webkit.WebSettings#setJavaScriptEnabled(boolean) 182 * setJavaScriptEnabled()}. </li> 183 * <li>Adding JavaScript-to-Java interfaces with the {@link 184 * android.webkit.WebView#addJavascriptInterface} method. 185 * This lets you bind Java objects into the WebView so they can be 186 * controlled from the web pages JavaScript.</li> 187 * </ul> 188 * 189 * <p>Here's a more complicated example, showing error handling, 190 * settings, and progress notification:</p> 191 * 192 * <pre class="prettyprint"> 193 * // Let's display the progress in the activity title bar, like the 194 * // browser app does. 195 * getWindow().requestFeature(Window.FEATURE_PROGRESS); 196 * 197 * webview.getSettings().setJavaScriptEnabled(true); 198 * 199 * final Activity activity = this; 200 * webview.setWebChromeClient(new WebChromeClient() { 201 * public void onProgressChanged(WebView view, int progress) { 202 * // Activities and WebViews measure progress with different scales. 203 * // The progress meter will automatically disappear when we reach 100% 204 * activity.setProgress(progress * 1000); 205 * } 206 * }); 207 * webview.setWebViewClient(new WebViewClient() { 208 * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 209 * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show(); 210 * } 211 * }); 212 * 213 * webview.loadUrl("http://slashdot.org/"); 214 * </pre> 215 * 216 * <h3>Cookie and window management</h3> 217 * 218 * <p>For obvious security reasons, your application has its own 219 * cache, cookie store etc.—it does not share the Browser 220 * application's data. Cookies are managed on a separate thread, so 221 * operations like index building don't block the UI 222 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager} 223 * if you want to use cookies in your application. 224 * </p> 225 * 226 * <p>By default, requests by the HTML to open new windows are 227 * ignored. This is true whether they be opened by JavaScript or by 228 * the target attribute on a link. You can customize your 229 * {@link WebChromeClient} to provide your own behaviour for opening multiple windows, 230 * and render them in whatever manner you want.</p> 231 * 232 * <p>The standard behavior for an Activity is to be destroyed and 233 * recreated when the device orientation or any other configuration changes. This will cause 234 * the WebView to reload the current page. If you don't want that, you 235 * can set your Activity to handle the {@code orientation} and {@code keyboardHidden} 236 * changes, and then just leave the WebView alone. It'll automatically 237 * re-orient itself as appropriate. Read <a 238 * href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a> for 239 * more information about how to handle configuration changes during runtime.</p> 240 * 241 * 242 * <h3>Building web pages to support different screen densities</h3> 243 * 244 * <p>The screen density of a device is based on the screen resolution. A screen with low density 245 * has fewer available pixels per inch, where a screen with high density 246 * has more — sometimes significantly more — pixels per inch. The density of a 247 * screen is important because, other things being equal, a UI element (such as a button) whose 248 * height and width are defined in terms of screen pixels will appear larger on the lower density 249 * screen and smaller on the higher density screen. 250 * For simplicity, Android collapses all actual screen densities into three generalized densities: 251 * high, medium, and low.</p> 252 * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default 253 * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen 254 * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels 255 * are bigger). 256 * Starting with API Level 5 (Android 2.0), WebView supports DOM, CSS, and meta tag features to help 257 * you (as a web developer) target screens with different screen densities.</p> 258 * <p>Here's a summary of the features you can use to handle different screen densities:</p> 259 * <ul> 260 * <li>The {@code window.devicePixelRatio} DOM property. The value of this property specifies the 261 * default scaling factor used for the current device. For example, if the value of {@code 262 * window.devicePixelRatio} is "1.0", then the device is considered a medium density (mdpi) device 263 * and default scaling is not applied to the web page; if the value is "1.5", then the device is 264 * considered a high density device (hdpi) and the page content is scaled 1.5x; if the 265 * value is "0.75", then the device is considered a low density device (ldpi) and the content is 266 * scaled 0.75x. However, if you specify the {@code "target-densitydpi"} meta property 267 * (discussed below), then you can stop this default scaling behavior.</li> 268 * <li>The {@code -webkit-device-pixel-ratio} CSS media query. Use this to specify the screen 269 * densities for which this style sheet is to be used. The corresponding value should be either 270 * "0.75", "1", or "1.5", to indicate that the styles are for devices with low density, medium 271 * density, or high density screens, respectively. For example: 272 * <pre> 273 * <link rel="stylesheet" media="screen and (-webkit-device-pixel-ratio:1.5)" href="hdpi.css" /></pre> 274 * <p>The {@code hdpi.css} stylesheet is only used for devices with a screen pixel ration of 1.5, 275 * which is the high density pixel ratio.</p> 276 * </li> 277 * <li>The {@code target-densitydpi} property for the {@code viewport} meta tag. You can use 278 * this to specify the target density for which the web page is designed, using the following 279 * values: 280 * <ul> 281 * <li>{@code device-dpi} - Use the device's native dpi as the target dpi. Default scaling never 282 * occurs.</li> 283 * <li>{@code high-dpi} - Use hdpi as the target dpi. Medium and low density screens scale down 284 * as appropriate.</li> 285 * <li>{@code medium-dpi} - Use mdpi as the target dpi. High density screens scale up and 286 * low density screens scale down. This is also the default behavior.</li> 287 * <li>{@code low-dpi} - Use ldpi as the target dpi. Medium and high density screens scale up 288 * as appropriate.</li> 289 * <li><em>{@code <value>}</em> - Specify a dpi value to use as the target dpi (accepted 290 * values are 70-400).</li> 291 * </ul> 292 * <p>Here's an example meta tag to specify the target density:</p> 293 * <pre><meta name="viewport" content="target-densitydpi=device-dpi" /></pre></li> 294 * </ul> 295 * <p>If you want to modify your web page for different densities, by using the {@code 296 * -webkit-device-pixel-ratio} CSS media query and/or the {@code 297 * window.devicePixelRatio} DOM property, then you should set the {@code target-densitydpi} meta 298 * property to {@code device-dpi}. This stops Android from performing scaling in your web page and 299 * allows you to make the necessary adjustments for each density via CSS and JavaScript.</p> 300 * 301 * 302 */ 303 @Widget 304 public class WebView extends AbsoluteLayout 305 implements ViewTreeObserver.OnGlobalFocusChangeListener, 306 ViewGroup.OnHierarchyChangeListener { 307 308 // enable debug output for drag trackers 309 private static final boolean DEBUG_DRAG_TRACKER = false; 310 // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing 311 // the screen all-the-time. Good for profiling our drawing code 312 static private final boolean AUTO_REDRAW_HACK = false; 313 // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK 314 private boolean mAutoRedraw; 315 316 static final String LOGTAG = "webview"; 317 318 private static class ExtendedZoomControls extends FrameLayout { 319 public ExtendedZoomControls(Context context, AttributeSet attrs) { 320 super(context, attrs); 321 LayoutInflater inflater = (LayoutInflater) 322 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 323 inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true); 324 mPlusMinusZoomControls = (ZoomControls) findViewById( 325 com.android.internal.R.id.zoomControls); 326 findViewById(com.android.internal.R.id.zoomMagnify).setVisibility( 327 View.GONE); 328 } 329 330 public void show(boolean showZoom, boolean canZoomOut) { 331 mPlusMinusZoomControls.setVisibility( 332 showZoom ? View.VISIBLE : View.GONE); 333 fade(View.VISIBLE, 0.0f, 1.0f); 334 } 335 336 public void hide() { 337 fade(View.GONE, 1.0f, 0.0f); 338 } 339 340 private void fade(int visibility, float startAlpha, float endAlpha) { 341 AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha); 342 anim.setDuration(500); 343 startAnimation(anim); 344 setVisibility(visibility); 345 } 346 347 public boolean hasFocus() { 348 return mPlusMinusZoomControls.hasFocus(); 349 } 350 351 public void setOnZoomInClickListener(OnClickListener listener) { 352 mPlusMinusZoomControls.setOnZoomInClickListener(listener); 353 } 354 355 public void setOnZoomOutClickListener(OnClickListener listener) { 356 mPlusMinusZoomControls.setOnZoomOutClickListener(listener); 357 } 358 359 ZoomControls mPlusMinusZoomControls; 360 } 361 362 /** 363 * Transportation object for returning WebView across thread boundaries. 364 */ 365 public class WebViewTransport { 366 private WebView mWebview; 367 368 /** 369 * Set the WebView to the transportation object. 370 * @param webview The WebView to transport. 371 */ 372 public synchronized void setWebView(WebView webview) { 373 mWebview = webview; 374 } 375 376 /** 377 * Return the WebView object. 378 * @return WebView The transported WebView object. 379 */ 380 public synchronized WebView getWebView() { 381 return mWebview; 382 } 383 } 384 385 // A final CallbackProxy shared by WebViewCore and BrowserFrame. 386 private final CallbackProxy mCallbackProxy; 387 388 private final WebViewDatabase mDatabase; 389 390 // SSL certificate for the main top-level page (if secure) 391 private SslCertificate mCertificate; 392 393 // Native WebView pointer that is 0 until the native object has been 394 // created. 395 private int mNativeClass; 396 // This would be final but it needs to be set to null when the WebView is 397 // destroyed. 398 private WebViewCore mWebViewCore; 399 // Handler for dispatching UI messages. 400 /* package */ final Handler mPrivateHandler = new PrivateHandler(); 401 private WebTextView mWebTextView; 402 // Used to ignore changes to webkit text that arrives to the UI side after 403 // more key events. 404 private int mTextGeneration; 405 406 // Used by WebViewCore to create child views. 407 /* package */ final ViewManager mViewManager; 408 409 // Used to display in full screen mode 410 PluginFullScreenHolder mFullScreenHolder; 411 412 /** 413 * Position of the last touch event. 414 */ 415 private float mLastTouchX; 416 private float mLastTouchY; 417 418 /** 419 * Time of the last touch event. 420 */ 421 private long mLastTouchTime; 422 423 /** 424 * Time of the last time sending touch event to WebViewCore 425 */ 426 private long mLastSentTouchTime; 427 428 /** 429 * The minimum elapsed time before sending another ACTION_MOVE event to 430 * WebViewCore. This really should be tuned for each type of the devices. 431 * For example in Google Map api test case, it takes Dream device at least 432 * 150ms to do a full cycle in the WebViewCore by processing a touch event, 433 * triggering the layout and drawing the picture. While the same process 434 * takes 60+ms on the current high speed device. If we make 435 * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent 436 * to WebViewCore queue and the real layout and draw events will be pushed 437 * to further, which slows down the refresh rate. Choose 50 to favor the 438 * current high speed devices. For Dream like devices, 100 is a better 439 * choice. Maybe make this in the buildspec later. 440 */ 441 private static final int TOUCH_SENT_INTERVAL = 50; 442 private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL; 443 444 /** 445 * Helper class to get velocity for fling 446 */ 447 VelocityTracker mVelocityTracker; 448 private int mMaximumFling; 449 private float mLastVelocity; 450 private float mLastVelX; 451 private float mLastVelY; 452 453 /** 454 * Touch mode 455 */ 456 private int mTouchMode = TOUCH_DONE_MODE; 457 private static final int TOUCH_INIT_MODE = 1; 458 private static final int TOUCH_DRAG_START_MODE = 2; 459 private static final int TOUCH_DRAG_MODE = 3; 460 private static final int TOUCH_SHORTPRESS_START_MODE = 4; 461 private static final int TOUCH_SHORTPRESS_MODE = 5; 462 private static final int TOUCH_DOUBLE_TAP_MODE = 6; 463 private static final int TOUCH_DONE_MODE = 7; 464 private static final int TOUCH_PINCH_DRAG = 8; 465 466 /** 467 * True if we have a touch panel capable of detecting smooth pan/scale at the same time 468 */ 469 private boolean mAllowPanAndScale; 470 471 // Whether to forward the touch events to WebCore 472 private boolean mForwardTouchEvents = false; 473 474 // Whether to prevent default during touch. The initial value depends on 475 // mForwardTouchEvents. If WebCore wants all the touch events, it says yes 476 // for touch down. Otherwise UI will wait for the answer of the first 477 // confirmed move before taking over the control. 478 private static final int PREVENT_DEFAULT_NO = 0; 479 private static final int PREVENT_DEFAULT_MAYBE_YES = 1; 480 private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2; 481 private static final int PREVENT_DEFAULT_YES = 3; 482 private static final int PREVENT_DEFAULT_IGNORE = 4; 483 private int mPreventDefault = PREVENT_DEFAULT_IGNORE; 484 485 // true when the touch movement exceeds the slop 486 private boolean mConfirmMove; 487 488 // if true, touch events will be first processed by WebCore, if prevent 489 // default is not set, the UI will continue handle them. 490 private boolean mDeferTouchProcess; 491 492 // to avoid interfering with the current touch events, track them 493 // separately. Currently no snapping or fling in the deferred process mode 494 private int mDeferTouchMode = TOUCH_DONE_MODE; 495 private float mLastDeferTouchX; 496 private float mLastDeferTouchY; 497 498 // To keep track of whether the current drag was initiated by a WebTextView, 499 // so that we know not to hide the cursor 500 boolean mDragFromTextInput; 501 502 // Whether or not to draw the cursor ring. 503 private boolean mDrawCursorRing = true; 504 505 // true if onPause has been called (and not onResume) 506 private boolean mIsPaused; 507 508 // true if, during a transition to a new page, we're delaying 509 // deleting a root layer until there's something to draw of the new page. 510 private boolean mDelayedDeleteRootLayer; 511 512 /** 513 * Customizable constant 514 */ 515 // pre-computed square of ViewConfiguration.getScaledTouchSlop() 516 private int mTouchSlopSquare; 517 // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop() 518 private int mDoubleTapSlopSquare; 519 // pre-computed density adjusted navigation slop 520 private int mNavSlop; 521 // This should be ViewConfiguration.getTapTimeout() 522 // But system time out is 100ms, which is too short for the browser. 523 // In the browser, if it switches out of tap too soon, jump tap won't work. 524 private static final int TAP_TIMEOUT = 200; 525 // This should be ViewConfiguration.getLongPressTimeout() 526 // But system time out is 500ms, which is too short for the browser. 527 // With a short timeout, it's difficult to treat trigger a short press. 528 private static final int LONG_PRESS_TIMEOUT = 1000; 529 // needed to avoid flinging after a pause of no movement 530 private static final int MIN_FLING_TIME = 250; 531 // draw unfiltered after drag is held without movement 532 private static final int MOTIONLESS_TIME = 100; 533 // The time that the Zoom Controls are visible before fading away 534 private static final long ZOOM_CONTROLS_TIMEOUT = 535 ViewConfiguration.getZoomControlsTimeout(); 536 // The amount of content to overlap between two screens when going through 537 // pages with the space bar, in pixels. 538 private static final int PAGE_SCROLL_OVERLAP = 24; 539 540 /** 541 * These prevent calling requestLayout if either dimension is fixed. This 542 * depends on the layout parameters and the measure specs. 543 */ 544 boolean mWidthCanMeasure; 545 boolean mHeightCanMeasure; 546 547 // Remember the last dimensions we sent to the native side so we can avoid 548 // sending the same dimensions more than once. 549 int mLastWidthSent; 550 int mLastHeightSent; 551 552 private int mContentWidth; // cache of value from WebViewCore 553 private int mContentHeight; // cache of value from WebViewCore 554 555 // Need to have the separate control for horizontal and vertical scrollbar 556 // style than the View's single scrollbar style 557 private boolean mOverlayHorizontalScrollbar = true; 558 private boolean mOverlayVerticalScrollbar = false; 559 560 // our standard speed. this way small distances will be traversed in less 561 // time than large distances, but we cap the duration, so that very large 562 // distances won't take too long to get there. 563 private static final int STD_SPEED = 480; // pixels per second 564 // time for the longest scroll animation 565 private static final int MAX_DURATION = 750; // milliseconds 566 private static final int SLIDE_TITLE_DURATION = 500; // milliseconds 567 private OverScroller mScroller; 568 private boolean mInOverScrollMode = false; 569 private static Paint mOverScrollBackground; 570 private static Paint mOverScrollBorder; 571 572 private boolean mWrapContent; 573 private static final int MOTIONLESS_FALSE = 0; 574 private static final int MOTIONLESS_PENDING = 1; 575 private static final int MOTIONLESS_TRUE = 2; 576 private static final int MOTIONLESS_IGNORE = 3; 577 private int mHeldMotionless; 578 579 // whether support multi-touch 580 private boolean mSupportMultiTouch; 581 // use the framework's ScaleGestureDetector to handle multi-touch 582 private ScaleGestureDetector mScaleDetector; 583 584 // the anchor point in the document space where VIEW_SIZE_CHANGED should 585 // apply to 586 private int mAnchorX; 587 private int mAnchorY; 588 589 /* 590 * Private message ids 591 */ 592 private static final int REMEMBER_PASSWORD = 1; 593 private static final int NEVER_REMEMBER_PASSWORD = 2; 594 private static final int SWITCH_TO_SHORTPRESS = 3; 595 private static final int SWITCH_TO_LONGPRESS = 4; 596 private static final int RELEASE_SINGLE_TAP = 5; 597 private static final int REQUEST_FORM_DATA = 6; 598 private static final int RESUME_WEBCORE_PRIORITY = 7; 599 private static final int DRAG_HELD_MOTIONLESS = 8; 600 private static final int AWAKEN_SCROLL_BARS = 9; 601 private static final int PREVENT_DEFAULT_TIMEOUT = 10; 602 603 private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD; 604 private static final int LAST_PRIVATE_MSG_ID = PREVENT_DEFAULT_TIMEOUT; 605 606 /* 607 * Package message ids 608 */ 609 //! arg1=x, arg2=y 610 static final int SCROLL_TO_MSG_ID = 101; 611 static final int SCROLL_BY_MSG_ID = 102; 612 //! arg1=x, arg2=y 613 static final int SPAWN_SCROLL_TO_MSG_ID = 103; 614 //! arg1=x, arg2=y 615 static final int SYNC_SCROLL_TO_MSG_ID = 104; 616 static final int NEW_PICTURE_MSG_ID = 105; 617 static final int UPDATE_TEXT_ENTRY_MSG_ID = 106; 618 static final int WEBCORE_INITIALIZED_MSG_ID = 107; 619 static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108; 620 static final int UPDATE_ZOOM_RANGE = 109; 621 static final int MOVE_OUT_OF_PLUGIN = 110; 622 static final int CLEAR_TEXT_ENTRY = 111; 623 static final int UPDATE_TEXT_SELECTION_MSG_ID = 112; 624 static final int SHOW_RECT_MSG_ID = 113; 625 static final int LONG_PRESS_CENTER = 114; 626 static final int PREVENT_TOUCH_ID = 115; 627 static final int WEBCORE_NEED_TOUCH_EVENTS = 116; 628 // obj=Rect in doc coordinates 629 static final int INVAL_RECT_MSG_ID = 117; 630 static final int REQUEST_KEYBOARD = 118; 631 static final int DO_MOTION_UP = 119; 632 static final int SHOW_FULLSCREEN = 120; 633 static final int HIDE_FULLSCREEN = 121; 634 static final int DOM_FOCUS_CHANGED = 122; 635 static final int IMMEDIATE_REPAINT_MSG_ID = 123; 636 static final int SET_ROOT_LAYER_MSG_ID = 124; 637 static final int RETURN_LABEL = 125; 638 static final int FIND_AGAIN = 126; 639 static final int CENTER_FIT_RECT = 127; 640 static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128; 641 static final int SET_SCROLLBAR_MODES = 129; 642 643 private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; 644 private static final int LAST_PACKAGE_MSG_ID = SET_SCROLLBAR_MODES; 645 646 static final String[] HandlerPrivateDebugString = { 647 "REMEMBER_PASSWORD", // = 1; 648 "NEVER_REMEMBER_PASSWORD", // = 2; 649 "SWITCH_TO_SHORTPRESS", // = 3; 650 "SWITCH_TO_LONGPRESS", // = 4; 651 "RELEASE_SINGLE_TAP", // = 5; 652 "REQUEST_FORM_DATA", // = 6; 653 "RESUME_WEBCORE_PRIORITY", // = 7; 654 "DRAG_HELD_MOTIONLESS", // = 8; 655 "AWAKEN_SCROLL_BARS", // = 9; 656 "PREVENT_DEFAULT_TIMEOUT" // = 10; 657 }; 658 659 static final String[] HandlerPackageDebugString = { 660 "SCROLL_TO_MSG_ID", // = 101; 661 "SCROLL_BY_MSG_ID", // = 102; 662 "SPAWN_SCROLL_TO_MSG_ID", // = 103; 663 "SYNC_SCROLL_TO_MSG_ID", // = 104; 664 "NEW_PICTURE_MSG_ID", // = 105; 665 "UPDATE_TEXT_ENTRY_MSG_ID", // = 106; 666 "WEBCORE_INITIALIZED_MSG_ID", // = 107; 667 "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 108; 668 "UPDATE_ZOOM_RANGE", // = 109; 669 "MOVE_OUT_OF_PLUGIN", // = 110; 670 "CLEAR_TEXT_ENTRY", // = 111; 671 "UPDATE_TEXT_SELECTION_MSG_ID", // = 112; 672 "SHOW_RECT_MSG_ID", // = 113; 673 "LONG_PRESS_CENTER", // = 114; 674 "PREVENT_TOUCH_ID", // = 115; 675 "WEBCORE_NEED_TOUCH_EVENTS", // = 116; 676 "INVAL_RECT_MSG_ID", // = 117; 677 "REQUEST_KEYBOARD", // = 118; 678 "DO_MOTION_UP", // = 119; 679 "SHOW_FULLSCREEN", // = 120; 680 "HIDE_FULLSCREEN", // = 121; 681 "DOM_FOCUS_CHANGED", // = 122; 682 "IMMEDIATE_REPAINT_MSG_ID", // = 123; 683 "SET_ROOT_LAYER_MSG_ID", // = 124; 684 "RETURN_LABEL", // = 125; 685 "FIND_AGAIN", // = 126; 686 "CENTER_FIT_RECT", // = 127; 687 "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128; 688 "SET_SCROLLBAR_MODES" // = 129; 689 }; 690 691 // If the site doesn't use the viewport meta tag to specify the viewport, 692 // use DEFAULT_VIEWPORT_WIDTH as the default viewport width 693 static final int DEFAULT_VIEWPORT_WIDTH = 800; 694 695 // normally we try to fit the content to the minimum preferred width 696 // calculated by the Webkit. To avoid the bad behavior when some site's 697 // minimum preferred width keeps growing when changing the viewport width or 698 // the minimum preferred width is huge, an upper limit is needed. 699 static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH; 700 701 // default scale limit. Depending on the display density 702 private static float DEFAULT_MAX_ZOOM_SCALE; 703 private static float DEFAULT_MIN_ZOOM_SCALE; 704 // scale limit, which can be set through viewport meta tag in the web page 705 private float mMaxZoomScale; 706 private float mMinZoomScale; 707 private boolean mMinZoomScaleFixed = true; 708 709 // initial scale in percent. 0 means using default. 710 private int mInitialScaleInPercent = 0; 711 712 // while in the zoom overview mode, the page's width is fully fit to the 713 // current window. The page is alive, in another words, you can click to 714 // follow the links. Double tap will toggle between zoom overview mode and 715 // the last zoom scale. 716 boolean mInZoomOverview = false; 717 718 // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn, 719 // engadget always have wider mContentWidth no matter what viewport size is. 720 int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH; 721 float mTextWrapScale; 722 723 // default scale. Depending on the display density. 724 static int DEFAULT_SCALE_PERCENT; 725 private float mDefaultScale; 726 727 private static float MINIMUM_SCALE_INCREMENT = 0.01f; 728 729 // set to true temporarily during ScaleGesture triggered zoom 730 private boolean mPreviewZoomOnly = false; 731 732 // computed scale and inverse, from mZoomWidth. 733 private float mActualScale; 734 private float mInvActualScale; 735 // if this is non-zero, it is used on drawing rather than mActualScale 736 private float mZoomScale; 737 private float mInvInitialZoomScale; 738 private float mInvFinalZoomScale; 739 private int mInitialScrollX; 740 private int mInitialScrollY; 741 private long mZoomStart; 742 private static final int ZOOM_ANIMATION_LENGTH = 500; 743 744 private boolean mUserScroll = false; 745 746 private int mSnapScrollMode = SNAP_NONE; 747 private static final int SNAP_NONE = 0; 748 private static final int SNAP_LOCK = 1; // not a separate state 749 private static final int SNAP_X = 2; // may be combined with SNAP_LOCK 750 private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK 751 private boolean mSnapPositive; 752 753 // keep these in sync with their counterparts in WebView.cpp 754 private static final int DRAW_EXTRAS_NONE = 0; 755 private static final int DRAW_EXTRAS_FIND = 1; 756 private static final int DRAW_EXTRAS_SELECTION = 2; 757 private static final int DRAW_EXTRAS_CURSOR_RING = 3; 758 759 // keep this in sync with WebCore:ScrollbarMode in WebKit 760 private static final int SCROLLBAR_AUTO = 0; 761 private static final int SCROLLBAR_ALWAYSOFF = 1; 762 // as we auto fade scrollbar, this is ignored. 763 private static final int SCROLLBAR_ALWAYSON = 2; 764 private int mHorizontalScrollBarMode = SCROLLBAR_AUTO; 765 private int mVerticalScrollBarMode = SCROLLBAR_AUTO; 766 767 /** 768 * Max distance to overscroll by in pixels. 769 * This how far content can be pulled beyond its normal bounds by the user. 770 */ 771 private int mOverscrollDistance; 772 773 /** 774 * Max distance to overfling by in pixels. 775 * This is how far flinged content can move beyond the end of its normal bounds. 776 */ 777 private int mOverflingDistance; 778 779 /* 780 * These manage the edge glow effect when flung or pulled beyond the edges. 781 * If one is not null, all are not null. Checking one for null is as good as checking each. 782 */ 783 private EdgeGlow mEdgeGlowTop; 784 private EdgeGlow mEdgeGlowBottom; 785 private EdgeGlow mEdgeGlowLeft; 786 private EdgeGlow mEdgeGlowRight; 787 /* 788 * These manage the delta the user has pulled beyond the edges. 789 */ 790 private int mOverscrollDeltaX; 791 private int mOverscrollDeltaY; 792 793 // Used to match key downs and key ups 794 private boolean mGotKeyDown; 795 796 /* package */ static boolean mLogEvent = true; 797 798 // for event log 799 private long mLastTouchUpTime = 0; 800 801 /** 802 * URI scheme for telephone number 803 */ 804 public static final String SCHEME_TEL = "tel:"; 805 /** 806 * URI scheme for email address 807 */ 808 public static final String SCHEME_MAILTO = "mailto:"; 809 /** 810 * URI scheme for map address 811 */ 812 public static final String SCHEME_GEO = "geo:0,0?q="; 813 814 private int mBackgroundColor = Color.WHITE; 815 816 // Used to notify listeners of a new picture. 817 private PictureListener mPictureListener; 818 /** 819 * Interface to listen for new pictures as they change. 820 */ 821 public interface PictureListener { 822 /** 823 * Notify the listener that the picture has changed. 824 * @param view The WebView that owns the picture. 825 * @param picture The new picture. 826 */ 827 public void onNewPicture(WebView view, Picture picture); 828 } 829 830 // FIXME: Want to make this public, but need to change the API file. 831 public /*static*/ class HitTestResult { 832 /** 833 * Default HitTestResult, where the target is unknown 834 */ 835 public static final int UNKNOWN_TYPE = 0; 836 /** 837 * HitTestResult for hitting a HTML::a tag 838 */ 839 public static final int ANCHOR_TYPE = 1; 840 /** 841 * HitTestResult for hitting a phone number 842 */ 843 public static final int PHONE_TYPE = 2; 844 /** 845 * HitTestResult for hitting a map address 846 */ 847 public static final int GEO_TYPE = 3; 848 /** 849 * HitTestResult for hitting an email address 850 */ 851 public static final int EMAIL_TYPE = 4; 852 /** 853 * HitTestResult for hitting an HTML::img tag 854 */ 855 public static final int IMAGE_TYPE = 5; 856 /** 857 * HitTestResult for hitting a HTML::a tag which contains HTML::img 858 */ 859 public static final int IMAGE_ANCHOR_TYPE = 6; 860 /** 861 * HitTestResult for hitting a HTML::a tag with src=http 862 */ 863 public static final int SRC_ANCHOR_TYPE = 7; 864 /** 865 * HitTestResult for hitting a HTML::a tag with src=http + HTML::img 866 */ 867 public static final int SRC_IMAGE_ANCHOR_TYPE = 8; 868 /** 869 * HitTestResult for hitting an edit text area 870 */ 871 public static final int EDIT_TEXT_TYPE = 9; 872 873 private int mType; 874 private String mExtra; 875 876 HitTestResult() { 877 mType = UNKNOWN_TYPE; 878 } 879 880 private void setType(int type) { 881 mType = type; 882 } 883 884 private void setExtra(String extra) { 885 mExtra = extra; 886 } 887 888 public int getType() { 889 return mType; 890 } 891 892 public String getExtra() { 893 return mExtra; 894 } 895 } 896 897 // The View containing the zoom controls 898 private ExtendedZoomControls mZoomControls; 899 private Runnable mZoomControlRunnable; 900 901 // mZoomButtonsController will be lazy initialized in 902 // getZoomButtonsController() to get better performance. 903 private ZoomButtonsController mZoomButtonsController; 904 905 // These keep track of the center point of the zoom. They are used to 906 // determine the point around which we should zoom. 907 private float mZoomCenterX; 908 private float mZoomCenterY; 909 910 private ZoomButtonsController.OnZoomListener mZoomListener = 911 new ZoomButtonsController.OnZoomListener() { 912 913 public void onVisibilityChanged(boolean visible) { 914 if (visible) { 915 switchOutDrawHistory(); 916 // Bring back the hidden zoom controls. 917 mZoomButtonsController.getZoomControls().setVisibility( 918 View.VISIBLE); 919 updateZoomButtonsEnabled(); 920 } 921 } 922 923 public void onZoom(boolean zoomIn) { 924 if (zoomIn) { 925 zoomIn(); 926 } else { 927 zoomOut(); 928 } 929 930 updateZoomButtonsEnabled(); 931 } 932 }; 933 934 /** 935 * Construct a new WebView with a Context object. 936 * @param context A Context object used to access application assets. 937 */ 938 public WebView(Context context) { 939 this(context, null); 940 } 941 942 /** 943 * Construct a new WebView with layout parameters. 944 * @param context A Context object used to access application assets. 945 * @param attrs An AttributeSet passed to our parent. 946 */ 947 public WebView(Context context, AttributeSet attrs) { 948 this(context, attrs, com.android.internal.R.attr.webViewStyle); 949 } 950 951 /** 952 * Construct a new WebView with layout parameters and a default style. 953 * @param context A Context object used to access application assets. 954 * @param attrs An AttributeSet passed to our parent. 955 * @param defStyle The default style resource ID. 956 */ 957 public WebView(Context context, AttributeSet attrs, int defStyle) { 958 this(context, attrs, defStyle, null); 959 } 960 961 /** 962 * Construct a new WebView with layout parameters, a default style and a set 963 * of custom Javscript interfaces to be added to the WebView at initialization 964 * time. This guarantees that these interfaces will be available when the JS 965 * context is initialized. 966 * @param context A Context object used to access application assets. 967 * @param attrs An AttributeSet passed to our parent. 968 * @param defStyle The default style resource ID. 969 * @param javascriptInterfaces is a Map of intareface names, as keys, and 970 * object implementing those interfaces, as values. 971 * @hide pending API council approval. 972 */ 973 protected WebView(Context context, AttributeSet attrs, int defStyle, 974 Map<String, Object> javascriptInterfaces) { 975 super(context, attrs, defStyle); 976 init(); 977 978 mCallbackProxy = new CallbackProxy(context, this); 979 mViewManager = new ViewManager(this); 980 mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); 981 mDatabase = WebViewDatabase.getInstance(context); 982 mScroller = new OverScroller(context); 983 984 updateMultiTouchSupport(context); 985 } 986 987 void updateMultiTouchSupport(Context context) { 988 WebSettings settings = getSettings(); 989 final PackageManager pm = context.getPackageManager(); 990 mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) 991 && settings.supportZoom() && settings.getBuiltInZoomControls(); 992 mAllowPanAndScale = pm.hasSystemFeature( 993 PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); 994 if (mSupportMultiTouch && (mScaleDetector == null)) { 995 mScaleDetector = new ScaleGestureDetector(context, 996 new ScaleDetectorListener()); 997 } else if (!mSupportMultiTouch && (mScaleDetector != null)) { 998 mScaleDetector = null; 999 } 1000 } 1001 1002 private void updateZoomButtonsEnabled() { 1003 if (mZoomButtonsController == null) return; 1004 boolean canZoomIn = mActualScale < mMaxZoomScale; 1005 boolean canZoomOut = mActualScale > mMinZoomScale && !mInZoomOverview; 1006 if (!canZoomIn && !canZoomOut) { 1007 // Hide the zoom in and out buttons, as well as the fit to page 1008 // button, if the page cannot zoom 1009 mZoomButtonsController.getZoomControls().setVisibility(View.GONE); 1010 } else { 1011 // Set each one individually, as a page may be able to zoom in 1012 // or out. 1013 mZoomButtonsController.setZoomInEnabled(canZoomIn); 1014 mZoomButtonsController.setZoomOutEnabled(canZoomOut); 1015 } 1016 } 1017 1018 private void init() { 1019 setWillNotDraw(false); 1020 setFocusable(true); 1021 setFocusableInTouchMode(true); 1022 setClickable(true); 1023 setLongClickable(true); 1024 1025 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 1026 int slop = configuration.getScaledTouchSlop(); 1027 mTouchSlopSquare = slop * slop; 1028 mMinLockSnapReverseDistance = slop; 1029 slop = configuration.getScaledDoubleTapSlop(); 1030 mDoubleTapSlopSquare = slop * slop; 1031 final float density = getContext().getResources().getDisplayMetrics().density; 1032 // use one line height, 16 based on our current default font, for how 1033 // far we allow a touch be away from the edge of a link 1034 mNavSlop = (int) (16 * density); 1035 // density adjusted scale factors 1036 DEFAULT_SCALE_PERCENT = (int) (100 * density); 1037 mDefaultScale = density; 1038 mActualScale = density; 1039 mInvActualScale = 1 / density; 1040 mTextWrapScale = density; 1041 DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; 1042 DEFAULT_MIN_ZOOM_SCALE = 0.25f * density; 1043 mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; 1044 mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; 1045 mMaximumFling = configuration.getScaledMaximumFlingVelocity(); 1046 mOverscrollDistance = configuration.getScaledOverscrollDistance(); 1047 mOverflingDistance = configuration.getScaledOverflingDistance(); 1048 } 1049 1050 @Override 1051 public void setOverScrollMode(int mode) { 1052 super.setOverScrollMode(mode); 1053 if (mode != OVER_SCROLL_NEVER) { 1054 if (mEdgeGlowTop == null) { 1055 final Resources res = getContext().getResources(); 1056 final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); 1057 final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); 1058 mEdgeGlowTop = new EdgeGlow(edge, glow); 1059 mEdgeGlowBottom = new EdgeGlow(edge, glow); 1060 mEdgeGlowLeft = new EdgeGlow(edge, glow); 1061 mEdgeGlowRight = new EdgeGlow(edge, glow); 1062 } 1063 } else { 1064 mEdgeGlowTop = null; 1065 mEdgeGlowBottom = null; 1066 mEdgeGlowLeft = null; 1067 mEdgeGlowRight = null; 1068 } 1069 } 1070 1071 /* package */void updateDefaultZoomDensity(int zoomDensity) { 1072 final float density = getContext().getResources().getDisplayMetrics().density 1073 * 100 / zoomDensity; 1074 if (Math.abs(density - mDefaultScale) > 0.01) { 1075 float scaleFactor = density / mDefaultScale; 1076 // adjust the limits 1077 mNavSlop = (int) (16 * density); 1078 DEFAULT_SCALE_PERCENT = (int) (100 * density); 1079 DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; 1080 DEFAULT_MIN_ZOOM_SCALE = 0.25f * density; 1081 mDefaultScale = density; 1082 mMaxZoomScale *= scaleFactor; 1083 mMinZoomScale *= scaleFactor; 1084 setNewZoomScale(mActualScale * scaleFactor, true, false); 1085 } 1086 } 1087 1088 /* package */ boolean onSavePassword(String schemePlusHost, String username, 1089 String password, final Message resumeMsg) { 1090 boolean rVal = false; 1091 if (resumeMsg == null) { 1092 // null resumeMsg implies saving password silently 1093 mDatabase.setUsernamePassword(schemePlusHost, username, password); 1094 } else { 1095 final Message remember = mPrivateHandler.obtainMessage( 1096 REMEMBER_PASSWORD); 1097 remember.getData().putString("host", schemePlusHost); 1098 remember.getData().putString("username", username); 1099 remember.getData().putString("password", password); 1100 remember.obj = resumeMsg; 1101 1102 final Message neverRemember = mPrivateHandler.obtainMessage( 1103 NEVER_REMEMBER_PASSWORD); 1104 neverRemember.getData().putString("host", schemePlusHost); 1105 neverRemember.getData().putString("username", username); 1106 neverRemember.getData().putString("password", password); 1107 neverRemember.obj = resumeMsg; 1108 1109 new AlertDialog.Builder(getContext()) 1110 .setTitle(com.android.internal.R.string.save_password_label) 1111 .setMessage(com.android.internal.R.string.save_password_message) 1112 .setPositiveButton(com.android.internal.R.string.save_password_notnow, 1113 new DialogInterface.OnClickListener() { 1114 public void onClick(DialogInterface dialog, int which) { 1115 resumeMsg.sendToTarget(); 1116 } 1117 }) 1118 .setNeutralButton(com.android.internal.R.string.save_password_remember, 1119 new DialogInterface.OnClickListener() { 1120 public void onClick(DialogInterface dialog, int which) { 1121 remember.sendToTarget(); 1122 } 1123 }) 1124 .setNegativeButton(com.android.internal.R.string.save_password_never, 1125 new DialogInterface.OnClickListener() { 1126 public void onClick(DialogInterface dialog, int which) { 1127 neverRemember.sendToTarget(); 1128 } 1129 }) 1130 .setOnCancelListener(new OnCancelListener() { 1131 public void onCancel(DialogInterface dialog) { 1132 resumeMsg.sendToTarget(); 1133 } 1134 }).show(); 1135 // Return true so that WebViewCore will pause while the dialog is 1136 // up. 1137 rVal = true; 1138 } 1139 return rVal; 1140 } 1141 1142 @Override 1143 public void setScrollBarStyle(int style) { 1144 if (style == View.SCROLLBARS_INSIDE_INSET 1145 || style == View.SCROLLBARS_OUTSIDE_INSET) { 1146 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false; 1147 } else { 1148 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true; 1149 } 1150 super.setScrollBarStyle(style); 1151 } 1152 1153 /** 1154 * Specify whether the horizontal scrollbar has overlay style. 1155 * @param overlay TRUE if horizontal scrollbar should have overlay style. 1156 */ 1157 public void setHorizontalScrollbarOverlay(boolean overlay) { 1158 mOverlayHorizontalScrollbar = overlay; 1159 } 1160 1161 /** 1162 * Specify whether the vertical scrollbar has overlay style. 1163 * @param overlay TRUE if vertical scrollbar should have overlay style. 1164 */ 1165 public void setVerticalScrollbarOverlay(boolean overlay) { 1166 mOverlayVerticalScrollbar = overlay; 1167 } 1168 1169 /** 1170 * Return whether horizontal scrollbar has overlay style 1171 * @return TRUE if horizontal scrollbar has overlay style. 1172 */ 1173 public boolean overlayHorizontalScrollbar() { 1174 return mOverlayHorizontalScrollbar; 1175 } 1176 1177 /** 1178 * Return whether vertical scrollbar has overlay style 1179 * @return TRUE if vertical scrollbar has overlay style. 1180 */ 1181 public boolean overlayVerticalScrollbar() { 1182 return mOverlayVerticalScrollbar; 1183 } 1184 1185 /* 1186 * Return the width of the view where the content of WebView should render 1187 * to. 1188 * Note: this can be called from WebCoreThread. 1189 */ 1190 /* package */ int getViewWidth() { 1191 if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) { 1192 return getWidth(); 1193 } else { 1194 return getWidth() - getVerticalScrollbarWidth(); 1195 } 1196 } 1197 1198 /* 1199 * returns the height of the titlebarview (if any). Does not care about 1200 * scrolling 1201 */ 1202 private int getTitleHeight() { 1203 return mTitleBar != null ? mTitleBar.getHeight() : 0; 1204 } 1205 1206 /* 1207 * Return the amount of the titlebarview (if any) that is visible 1208 */ 1209 private int getVisibleTitleHeight() { 1210 // need to restrict mScrollY due to over scroll 1211 return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0); 1212 } 1213 1214 /* 1215 * Return the height of the view where the content of WebView should render 1216 * to. Note that this excludes mTitleBar, if there is one. 1217 * Note: this can be called from WebCoreThread. 1218 */ 1219 /* package */ int getViewHeight() { 1220 return getViewHeightWithTitle() - getVisibleTitleHeight(); 1221 } 1222 1223 private int getViewHeightWithTitle() { 1224 int height = getHeight(); 1225 if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) { 1226 height -= getHorizontalScrollbarHeight(); 1227 } 1228 return height; 1229 } 1230 1231 /** 1232 * @return The SSL certificate for the main top-level page or null if 1233 * there is no certificate (the site is not secure). 1234 */ 1235 public SslCertificate getCertificate() { 1236 return mCertificate; 1237 } 1238 1239 /** 1240 * Sets the SSL certificate for the main top-level page. 1241 */ 1242 public void setCertificate(SslCertificate certificate) { 1243 if (DebugFlags.WEB_VIEW) { 1244 Log.v(LOGTAG, "setCertificate=" + certificate); 1245 } 1246 // here, the certificate can be null (if the site is not secure) 1247 mCertificate = certificate; 1248 } 1249 1250 //------------------------------------------------------------------------- 1251 // Methods called by activity 1252 //------------------------------------------------------------------------- 1253 1254 /** 1255 * Save the username and password for a particular host in the WebView's 1256 * internal database. 1257 * @param host The host that required the credentials. 1258 * @param username The username for the given host. 1259 * @param password The password for the given host. 1260 */ 1261 public void savePassword(String host, String username, String password) { 1262 mDatabase.setUsernamePassword(host, username, password); 1263 } 1264 1265 /** 1266 * Set the HTTP authentication credentials for a given host and realm. 1267 * 1268 * @param host The host for the credentials. 1269 * @param realm The realm for the credentials. 1270 * @param username The username for the password. If it is null, it means 1271 * password can't be saved. 1272 * @param password The password 1273 */ 1274 public void setHttpAuthUsernamePassword(String host, String realm, 1275 String username, String password) { 1276 mDatabase.setHttpAuthUsernamePassword(host, realm, username, password); 1277 } 1278 1279 /** 1280 * Retrieve the HTTP authentication username and password for a given 1281 * host & realm pair 1282 * 1283 * @param host The host for which the credentials apply. 1284 * @param realm The realm for which the credentials apply. 1285 * @return String[] if found, String[0] is username, which can be null and 1286 * String[1] is password. Return null if it can't find anything. 1287 */ 1288 public String[] getHttpAuthUsernamePassword(String host, String realm) { 1289 return mDatabase.getHttpAuthUsernamePassword(host, realm); 1290 } 1291 1292 private void clearHelpers() { 1293 clearTextEntry(false); 1294 selectionDone(); 1295 } 1296 1297 /** 1298 * Destroy the internal state of the WebView. This method should be called 1299 * after the WebView has been removed from the view system. No other 1300 * methods may be called on a WebView after destroy. 1301 */ 1302 public void destroy() { 1303 clearHelpers(); 1304 if (mWebViewCore != null) { 1305 // Set the handlers to null before destroying WebViewCore so no 1306 // more messages will be posted. 1307 mCallbackProxy.setWebViewClient(null); 1308 mCallbackProxy.setWebChromeClient(null); 1309 // Tell WebViewCore to destroy itself 1310 synchronized (this) { 1311 WebViewCore webViewCore = mWebViewCore; 1312 mWebViewCore = null; // prevent using partial webViewCore 1313 webViewCore.destroy(); 1314 } 1315 // Remove any pending messages that might not be serviced yet. 1316 mPrivateHandler.removeCallbacksAndMessages(null); 1317 mCallbackProxy.removeCallbacksAndMessages(null); 1318 // Wake up the WebCore thread just in case it is waiting for a 1319 // javascript dialog. 1320 synchronized (mCallbackProxy) { 1321 mCallbackProxy.notify(); 1322 } 1323 } 1324 if (mNativeClass != 0) { 1325 nativeDestroy(); 1326 mNativeClass = 0; 1327 } 1328 } 1329 1330 /** 1331 * Enables platform notifications of data state and proxy changes. 1332 */ 1333 public static void enablePlatformNotifications() { 1334 Network.enablePlatformNotifications(); 1335 } 1336 1337 /** 1338 * If platform notifications are enabled, this should be called 1339 * from the Activity's onPause() or onStop(). 1340 */ 1341 public static void disablePlatformNotifications() { 1342 Network.disablePlatformNotifications(); 1343 } 1344 1345 /** 1346 * Sets JavaScript engine flags. 1347 * 1348 * @param flags JS engine flags in a String 1349 * 1350 * @hide pending API solidification 1351 */ 1352 public void setJsFlags(String flags) { 1353 mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags); 1354 } 1355 1356 /** 1357 * Inform WebView of the network state. This is used to set 1358 * the javascript property window.navigator.isOnline and 1359 * generates the online/offline event as specified in HTML5, sec. 5.7.7 1360 * @param networkUp boolean indicating if network is available 1361 */ 1362 public void setNetworkAvailable(boolean networkUp) { 1363 mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE, 1364 networkUp ? 1 : 0, 0); 1365 } 1366 1367 /** 1368 * Inform WebView about the current network type. 1369 * {@hide} 1370 */ 1371 public void setNetworkType(String type, String subtype) { 1372 Map<String, String> map = new HashMap<String, String>(); 1373 map.put("type", type); 1374 map.put("subtype", subtype); 1375 mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map); 1376 } 1377 /** 1378 * Save the state of this WebView used in 1379 * {@link android.app.Activity#onSaveInstanceState}. Please note that this 1380 * method no longer stores the display data for this WebView. The previous 1381 * behavior could potentially leak files if {@link #restoreState} was never 1382 * called. See {@link #savePicture} and {@link #restorePicture} for saving 1383 * and restoring the display data. 1384 * @param outState The Bundle to store the WebView state. 1385 * @return The same copy of the back/forward list used to save the state. If 1386 * saveState fails, the returned list will be null. 1387 * @see #savePicture 1388 * @see #restorePicture 1389 */ 1390 public WebBackForwardList saveState(Bundle outState) { 1391 if (outState == null) { 1392 return null; 1393 } 1394 // We grab a copy of the back/forward list because a client of WebView 1395 // may have invalidated the history list by calling clearHistory. 1396 WebBackForwardList list = copyBackForwardList(); 1397 final int currentIndex = list.getCurrentIndex(); 1398 final int size = list.getSize(); 1399 // We should fail saving the state if the list is empty or the index is 1400 // not in a valid range. 1401 if (currentIndex < 0 || currentIndex >= size || size == 0) { 1402 return null; 1403 } 1404 outState.putInt("index", currentIndex); 1405 // FIXME: This should just be a byte[][] instead of ArrayList but 1406 // Parcel.java does not have the code to handle multi-dimensional 1407 // arrays. 1408 ArrayList<byte[]> history = new ArrayList<byte[]>(size); 1409 for (int i = 0; i < size; i++) { 1410 WebHistoryItem item = list.getItemAtIndex(i); 1411 if (null == item) { 1412 // FIXME: this shouldn't happen 1413 // need to determine how item got set to null 1414 Log.w(LOGTAG, "saveState: Unexpected null history item."); 1415 return null; 1416 } 1417 byte[] data = item.getFlattenedData(); 1418 if (data == null) { 1419 // It would be very odd to not have any data for a given history 1420 // item. And we will fail to rebuild the history list without 1421 // flattened data. 1422 return null; 1423 } 1424 history.add(data); 1425 } 1426 outState.putSerializable("history", history); 1427 if (mCertificate != null) { 1428 outState.putBundle("certificate", 1429 SslCertificate.saveState(mCertificate)); 1430 } 1431 return list; 1432 } 1433 1434 /** 1435 * Save the current display data to the Bundle given. Used in conjunction 1436 * with {@link #saveState}. 1437 * @param b A Bundle to store the display data. 1438 * @param dest The file to store the serialized picture data. Will be 1439 * overwritten with this WebView's picture data. 1440 * @return True if the picture was successfully saved. 1441 */ 1442 public boolean savePicture(Bundle b, final File dest) { 1443 if (dest == null || b == null) { 1444 return false; 1445 } 1446 final Picture p = capturePicture(); 1447 // Use a temporary file while writing to ensure the destination file 1448 // contains valid data. 1449 final File temp = new File(dest.getPath() + ".writing"); 1450 new Thread(new Runnable() { 1451 public void run() { 1452 FileOutputStream out = null; 1453 try { 1454 out = new FileOutputStream(temp); 1455 p.writeToStream(out); 1456 // Writing the picture succeeded, rename the temporary file 1457 // to the destination. 1458 temp.renameTo(dest); 1459 } catch (Exception e) { 1460 // too late to do anything about it. 1461 } finally { 1462 if (out != null) { 1463 try { 1464 out.close(); 1465 } catch (Exception e) { 1466 // Can't do anything about that 1467 } 1468 } 1469 temp.delete(); 1470 } 1471 } 1472 }).start(); 1473 // now update the bundle 1474 b.putInt("scrollX", mScrollX); 1475 b.putInt("scrollY", mScrollY); 1476 b.putFloat("scale", mActualScale); 1477 b.putFloat("textwrapScale", mTextWrapScale); 1478 b.putBoolean("overview", mInZoomOverview); 1479 return true; 1480 } 1481 1482 private void restoreHistoryPictureFields(Picture p, Bundle b) { 1483 int sx = b.getInt("scrollX", 0); 1484 int sy = b.getInt("scrollY", 0); 1485 float scale = b.getFloat("scale", 1.0f); 1486 mDrawHistory = true; 1487 mHistoryPicture = p; 1488 mScrollX = sx; 1489 mScrollY = sy; 1490 mHistoryWidth = Math.round(p.getWidth() * scale); 1491 mHistoryHeight = Math.round(p.getHeight() * scale); 1492 // as getWidth() / getHeight() of the view are not available yet, set up 1493 // mActualScale, so that when onSizeChanged() is called, the rest will 1494 // be set correctly 1495 mActualScale = scale; 1496 mInvActualScale = 1 / scale; 1497 mTextWrapScale = b.getFloat("textwrapScale", scale); 1498 mInZoomOverview = b.getBoolean("overview"); 1499 invalidate(); 1500 } 1501 1502 /** 1503 * Restore the display data that was save in {@link #savePicture}. Used in 1504 * conjunction with {@link #restoreState}. 1505 * @param b A Bundle containing the saved display data. 1506 * @param src The file where the picture data was stored. 1507 * @return True if the picture was successfully restored. 1508 */ 1509 public boolean restorePicture(Bundle b, File src) { 1510 if (src == null || b == null) { 1511 return false; 1512 } 1513 if (!src.exists()) { 1514 return false; 1515 } 1516 try { 1517 final FileInputStream in = new FileInputStream(src); 1518 final Bundle copy = new Bundle(b); 1519 new Thread(new Runnable() { 1520 public void run() { 1521 try { 1522 final Picture p = Picture.createFromStream(in); 1523 if (p != null) { 1524 // Post a runnable on the main thread to update the 1525 // history picture fields. 1526 mPrivateHandler.post(new Runnable() { 1527 public void run() { 1528 restoreHistoryPictureFields(p, copy); 1529 } 1530 }); 1531 } 1532 } finally { 1533 try { 1534 in.close(); 1535 } catch (Exception e) { 1536 // Nothing we can do now. 1537 } 1538 } 1539 } 1540 }).start(); 1541 } catch (FileNotFoundException e){ 1542 e.printStackTrace(); 1543 } 1544 return true; 1545 } 1546 1547 /** 1548 * Restore the state of this WebView from the given map used in 1549 * {@link android.app.Activity#onRestoreInstanceState}. This method should 1550 * be called to restore the state of the WebView before using the object. If 1551 * it is called after the WebView has had a chance to build state (load 1552 * pages, create a back/forward list, etc.) there may be undesirable 1553 * side-effects. Please note that this method no longer restores the 1554 * display data for this WebView. See {@link #savePicture} and {@link 1555 * #restorePicture} for saving and restoring the display data. 1556 * @param inState The incoming Bundle of state. 1557 * @return The restored back/forward list or null if restoreState failed. 1558 * @see #savePicture 1559 * @see #restorePicture 1560 */ 1561 public WebBackForwardList restoreState(Bundle inState) { 1562 WebBackForwardList returnList = null; 1563 if (inState == null) { 1564 return returnList; 1565 } 1566 if (inState.containsKey("index") && inState.containsKey("history")) { 1567 mCertificate = SslCertificate.restoreState( 1568 inState.getBundle("certificate")); 1569 1570 final WebBackForwardList list = mCallbackProxy.getBackForwardList(); 1571 final int index = inState.getInt("index"); 1572 // We can't use a clone of the list because we need to modify the 1573 // shared copy, so synchronize instead to prevent concurrent 1574 // modifications. 1575 synchronized (list) { 1576 final List<byte[]> history = 1577 (List<byte[]>) inState.getSerializable("history"); 1578 final int size = history.size(); 1579 // Check the index bounds so we don't crash in native code while 1580 // restoring the history index. 1581 if (index < 0 || index >= size) { 1582 return null; 1583 } 1584 for (int i = 0; i < size; i++) { 1585 byte[] data = history.remove(0); 1586 if (data == null) { 1587 // If we somehow have null data, we cannot reconstruct 1588 // the item and thus our history list cannot be rebuilt. 1589 return null; 1590 } 1591 WebHistoryItem item = new WebHistoryItem(data); 1592 list.addHistoryItem(item); 1593 } 1594 // Grab the most recent copy to return to the caller. 1595 returnList = copyBackForwardList(); 1596 // Update the copy to have the correct index. 1597 returnList.setCurrentIndex(index); 1598 } 1599 // Remove all pending messages because we are restoring previous 1600 // state. 1601 mWebViewCore.removeMessages(); 1602 // Send a restore state message. 1603 mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); 1604 } 1605 return returnList; 1606 } 1607 1608 /** 1609 * Load the given url with the extra headers. 1610 * @param url The url of the resource to load. 1611 * @param extraHeaders The extra headers sent with this url. This should not 1612 * include the common headers like "user-agent". If it does, it 1613 * will be replaced by the intrinsic value of the WebView. 1614 */ 1615 public void loadUrl(String url, Map<String, String> extraHeaders) { 1616 switchOutDrawHistory(); 1617 WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData(); 1618 arg.mUrl = url; 1619 arg.mExtraHeaders = extraHeaders; 1620 mWebViewCore.sendMessage(EventHub.LOAD_URL, arg); 1621 clearHelpers(); 1622 } 1623 1624 /** 1625 * Load the given url. 1626 * @param url The url of the resource to load. 1627 */ 1628 public void loadUrl(String url) { 1629 if (url == null) { 1630 return; 1631 } 1632 loadUrl(url, null); 1633 } 1634 1635 /** 1636 * Load the url with postData using "POST" method into the WebView. If url 1637 * is not a network url, it will be loaded with {link 1638 * {@link #loadUrl(String)} instead. 1639 * 1640 * @param url The url of the resource to load. 1641 * @param postData The data will be passed to "POST" request. 1642 */ 1643 public void postUrl(String url, byte[] postData) { 1644 if (URLUtil.isNetworkUrl(url)) { 1645 switchOutDrawHistory(); 1646 WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData(); 1647 arg.mUrl = url; 1648 arg.mPostData = postData; 1649 mWebViewCore.sendMessage(EventHub.POST_URL, arg); 1650 clearHelpers(); 1651 } else { 1652 loadUrl(url); 1653 } 1654 } 1655 1656 /** 1657 * Load the given data into the WebView. This will load the data into 1658 * WebView using the data: scheme. Content loaded through this mechanism 1659 * does not have the ability to load content from the network. 1660 * @param data A String of data in the given encoding. The date must 1661 * be URI-escaped -- '#', '%', '\', '?' should be replaced by %23, %25, 1662 * %27, %3f respectively. 1663 * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg 1664 * @param encoding The encoding of the data. i.e. utf-8, base64 1665 */ 1666 public void loadData(String data, String mimeType, String encoding) { 1667 loadUrl("data:" + mimeType + ";" + encoding + "," + data); 1668 } 1669 1670 /** 1671 * Load the given data into the WebView, use the provided URL as the base 1672 * URL for the content. The base URL is the URL that represents the page 1673 * that is loaded through this interface. As such, it is used to resolve any 1674 * relative URLs. The historyUrl is used for the history entry. 1675 * <p> 1676 * Note for post 1.0. Due to the change in the WebKit, the access to asset 1677 * files through "file:///android_asset/" for the sub resources is more 1678 * restricted. If you provide null or empty string as baseUrl, you won't be 1679 * able to access asset files. If the baseUrl is anything other than 1680 * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for 1681 * sub resources. 1682 * 1683 * @param baseUrl Url to resolve relative paths with, if null defaults to 1684 * "about:blank" 1685 * @param data A String of data in the given encoding. 1686 * @param mimeType The MIMEType of the data. i.e. text/html. If null, 1687 * defaults to "text/html" 1688 * @param encoding The encoding of the data. i.e. utf-8, us-ascii 1689 * @param historyUrl URL to use as the history entry. Can be null. 1690 */ 1691 public void loadDataWithBaseURL(String baseUrl, String data, 1692 String mimeType, String encoding, String historyUrl) { 1693 1694 if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { 1695 loadData(data, mimeType, encoding); 1696 return; 1697 } 1698 switchOutDrawHistory(); 1699 WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData(); 1700 arg.mBaseUrl = baseUrl; 1701 arg.mData = data; 1702 arg.mMimeType = mimeType; 1703 arg.mEncoding = encoding; 1704 arg.mHistoryUrl = historyUrl; 1705 mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg); 1706 clearHelpers(); 1707 } 1708 1709 /** 1710 * Stop the current load. 1711 */ 1712 public void stopLoading() { 1713 // TODO: should we clear all the messages in the queue before sending 1714 // STOP_LOADING? 1715 switchOutDrawHistory(); 1716 mWebViewCore.sendMessage(EventHub.STOP_LOADING); 1717 } 1718 1719 /** 1720 * Reload the current url. 1721 */ 1722 public void reload() { 1723 clearHelpers(); 1724 switchOutDrawHistory(); 1725 mWebViewCore.sendMessage(EventHub.RELOAD); 1726 } 1727 1728 /** 1729 * Return true if this WebView has a back history item. 1730 * @return True iff this WebView has a back history item. 1731 */ 1732 public boolean canGoBack() { 1733 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1734 synchronized (l) { 1735 if (l.getClearPending()) { 1736 return false; 1737 } else { 1738 return l.getCurrentIndex() > 0; 1739 } 1740 } 1741 } 1742 1743 /** 1744 * Go back in the history of this WebView. 1745 */ 1746 public void goBack() { 1747 goBackOrForward(-1); 1748 } 1749 1750 /** 1751 * Return true if this WebView has a forward history item. 1752 * @return True iff this Webview has a forward history item. 1753 */ 1754 public boolean canGoForward() { 1755 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1756 synchronized (l) { 1757 if (l.getClearPending()) { 1758 return false; 1759 } else { 1760 return l.getCurrentIndex() < l.getSize() - 1; 1761 } 1762 } 1763 } 1764 1765 /** 1766 * Go forward in the history of this WebView. 1767 */ 1768 public void goForward() { 1769 goBackOrForward(1); 1770 } 1771 1772 /** 1773 * Return true if the page can go back or forward the given 1774 * number of steps. 1775 * @param steps The negative or positive number of steps to move the 1776 * history. 1777 */ 1778 public boolean canGoBackOrForward(int steps) { 1779 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1780 synchronized (l) { 1781 if (l.getClearPending()) { 1782 return false; 1783 } else { 1784 int newIndex = l.getCurrentIndex() + steps; 1785 return newIndex >= 0 && newIndex < l.getSize(); 1786 } 1787 } 1788 } 1789 1790 /** 1791 * Go to the history item that is the number of steps away from 1792 * the current item. Steps is negative if backward and positive 1793 * if forward. 1794 * @param steps The number of steps to take back or forward in the back 1795 * forward list. 1796 */ 1797 public void goBackOrForward(int steps) { 1798 goBackOrForward(steps, false); 1799 } 1800 1801 private void goBackOrForward(int steps, boolean ignoreSnapshot) { 1802 if (steps != 0) { 1803 clearHelpers(); 1804 mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps, 1805 ignoreSnapshot ? 1 : 0); 1806 } 1807 } 1808 1809 private boolean extendScroll(int y) { 1810 int finalY = mScroller.getFinalY(); 1811 int newY = pinLocY(finalY + y); 1812 if (newY == finalY) return false; 1813 mScroller.setFinalY(newY); 1814 mScroller.extendDuration(computeDuration(0, y)); 1815 return true; 1816 } 1817 1818 /** 1819 * Scroll the contents of the view up by half the view size 1820 * @param top true to jump to the top of the page 1821 * @return true if the page was scrolled 1822 */ 1823 public boolean pageUp(boolean top) { 1824 if (mNativeClass == 0) { 1825 return false; 1826 } 1827 nativeClearCursor(); // start next trackball movement from page edge 1828 if (top) { 1829 // go to the top of the document 1830 return pinScrollTo(mScrollX, 0, true, 0); 1831 } 1832 // Page up 1833 int h = getHeight(); 1834 int y; 1835 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1836 y = -h + PAGE_SCROLL_OVERLAP; 1837 } else { 1838 y = -h / 2; 1839 } 1840 mUserScroll = true; 1841 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 1842 : extendScroll(y); 1843 } 1844 1845 /** 1846 * Scroll the contents of the view down by half the page size 1847 * @param bottom true to jump to bottom of page 1848 * @return true if the page was scrolled 1849 */ 1850 public boolean pageDown(boolean bottom) { 1851 if (mNativeClass == 0) { 1852 return false; 1853 } 1854 nativeClearCursor(); // start next trackball movement from page edge 1855 if (bottom) { 1856 return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0); 1857 } 1858 // Page down. 1859 int h = getHeight(); 1860 int y; 1861 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1862 y = h - PAGE_SCROLL_OVERLAP; 1863 } else { 1864 y = h / 2; 1865 } 1866 mUserScroll = true; 1867 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 1868 : extendScroll(y); 1869 } 1870 1871 /** 1872 * Clear the view so that onDraw() will draw nothing but white background, 1873 * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY 1874 */ 1875 public void clearView() { 1876 mContentWidth = 0; 1877 mContentHeight = 0; 1878 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); 1879 } 1880 1881 /** 1882 * Return a new picture that captures the current display of the webview. 1883 * This is a copy of the display, and will be unaffected if the webview 1884 * later loads a different URL. 1885 * 1886 * @return a picture containing the current contents of the view. Note this 1887 * picture is of the entire document, and is not restricted to the 1888 * bounds of the view. 1889 */ 1890 public Picture capturePicture() { 1891 if (null == mWebViewCore) return null; // check for out of memory tab 1892 return mWebViewCore.copyContentPicture(); 1893 } 1894 1895 /** 1896 * Return true if the browser is displaying a TextView for text input. 1897 */ 1898 private boolean inEditingMode() { 1899 return mWebTextView != null && mWebTextView.getParent() != null; 1900 } 1901 1902 /** 1903 * Remove the WebTextView. 1904 * @param disableFocusController If true, send a message to webkit 1905 * disabling the focus controller, so the caret stops blinking. 1906 */ 1907 private void clearTextEntry(boolean disableFocusController) { 1908 if (inEditingMode()) { 1909 mWebTextView.remove(); 1910 if (disableFocusController) { 1911 setFocusControllerInactive(); 1912 } 1913 } 1914 } 1915 1916 /** 1917 * Return the current scale of the WebView 1918 * @return The current scale. 1919 */ 1920 public float getScale() { 1921 return mActualScale; 1922 } 1923 1924 /** 1925 * Set the initial scale for the WebView. 0 means default. If 1926 * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the 1927 * way. Otherwise it starts with 100%. If initial scale is greater than 0, 1928 * WebView starts will this value as initial scale. 1929 * 1930 * @param scaleInPercent The initial scale in percent. 1931 */ 1932 public void setInitialScale(int scaleInPercent) { 1933 mInitialScaleInPercent = scaleInPercent; 1934 } 1935 1936 /** 1937 * Invoke the graphical zoom picker widget for this WebView. This will 1938 * result in the zoom widget appearing on the screen to control the zoom 1939 * level of this WebView. 1940 */ 1941 public void invokeZoomPicker() { 1942 if (!getSettings().supportZoom()) { 1943 Log.w(LOGTAG, "This WebView doesn't support zoom."); 1944 return; 1945 } 1946 clearTextEntry(false); 1947 if (getSettings().getBuiltInZoomControls()) { 1948 getZoomButtonsController().setVisible(true); 1949 } else { 1950 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 1951 mPrivateHandler.postDelayed(mZoomControlRunnable, 1952 ZOOM_CONTROLS_TIMEOUT); 1953 } 1954 } 1955 1956 /** 1957 * Return a HitTestResult based on the current cursor node. If a HTML::a tag 1958 * is found and the anchor has a non-javascript url, the HitTestResult type 1959 * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the 1960 * anchor does not have a url or if it is a javascript url, the type will 1961 * be UNKNOWN_TYPE and the url has to be retrieved through 1962 * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is 1963 * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in 1964 * the "extra" field. A type of 1965 * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as 1966 * a child node. If a phone number is found, the HitTestResult type is set 1967 * to PHONE_TYPE and the phone number is set in the "extra" field of 1968 * HitTestResult. If a map address is found, the HitTestResult type is set 1969 * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. 1970 * If an email address is found, the HitTestResult type is set to EMAIL_TYPE 1971 * and the email is set in the "extra" field of HitTestResult. Otherwise, 1972 * HitTestResult type is set to UNKNOWN_TYPE. 1973 */ 1974 public HitTestResult getHitTestResult() { 1975 if (mNativeClass == 0) { 1976 return null; 1977 } 1978 1979 HitTestResult result = new HitTestResult(); 1980 if (nativeHasCursorNode()) { 1981 if (nativeCursorIsTextInput()) { 1982 result.setType(HitTestResult.EDIT_TEXT_TYPE); 1983 } else { 1984 String text = nativeCursorText(); 1985 if (text != null) { 1986 if (text.startsWith(SCHEME_TEL)) { 1987 result.setType(HitTestResult.PHONE_TYPE); 1988 result.setExtra(text.substring(SCHEME_TEL.length())); 1989 } else if (text.startsWith(SCHEME_MAILTO)) { 1990 result.setType(HitTestResult.EMAIL_TYPE); 1991 result.setExtra(text.substring(SCHEME_MAILTO.length())); 1992 } else if (text.startsWith(SCHEME_GEO)) { 1993 result.setType(HitTestResult.GEO_TYPE); 1994 result.setExtra(URLDecoder.decode(text 1995 .substring(SCHEME_GEO.length()))); 1996 } else if (nativeCursorIsAnchor()) { 1997 result.setType(HitTestResult.SRC_ANCHOR_TYPE); 1998 result.setExtra(text); 1999 } 2000 } 2001 } 2002 } 2003 int type = result.getType(); 2004 if (type == HitTestResult.UNKNOWN_TYPE 2005 || type == HitTestResult.SRC_ANCHOR_TYPE) { 2006 // Now check to see if it is an image. 2007 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 2008 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 2009 String text = nativeImageURI(contentX, contentY); 2010 if (text != null) { 2011 result.setType(type == HitTestResult.UNKNOWN_TYPE ? 2012 HitTestResult.IMAGE_TYPE : 2013 HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 2014 result.setExtra(text); 2015 } 2016 } 2017 return result; 2018 } 2019 2020 // Called by JNI when the DOM has changed the focus. Clear the focus so 2021 // that new keys will go to the newly focused field 2022 private void domChangedFocus() { 2023 if (inEditingMode()) { 2024 mPrivateHandler.obtainMessage(DOM_FOCUS_CHANGED).sendToTarget(); 2025 } 2026 } 2027 /** 2028 * Request the href of an anchor element due to getFocusNodePath returning 2029 * "href." If hrefMsg is null, this method returns immediately and does not 2030 * dispatch hrefMsg to its target. 2031 * 2032 * @param hrefMsg This message will be dispatched with the result of the 2033 * request as the data member with "url" as key. The result can 2034 * be null. 2035 */ 2036 // FIXME: API change required to change the name of this function. We now 2037 // look at the cursor node, and not the focus node. Also, what is 2038 // getFocusNodePath? 2039 public void requestFocusNodeHref(Message hrefMsg) { 2040 if (hrefMsg == null || mNativeClass == 0) { 2041 return; 2042 } 2043 if (nativeCursorIsAnchor()) { 2044 mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF, 2045 nativeCursorFramePointer(), nativeCursorNodePointer(), 2046 hrefMsg); 2047 } 2048 } 2049 2050 /** 2051 * Request the url of the image last touched by the user. msg will be sent 2052 * to its target with a String representing the url as its object. 2053 * 2054 * @param msg This message will be dispatched with the result of the request 2055 * as the data member with "url" as key. The result can be null. 2056 */ 2057 public void requestImageRef(Message msg) { 2058 if (0 == mNativeClass) return; // client isn't initialized 2059 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 2060 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 2061 String ref = nativeImageURI(contentX, contentY); 2062 Bundle data = msg.getData(); 2063 data.putString("url", ref); 2064 msg.setData(data); 2065 msg.sendToTarget(); 2066 } 2067 2068 private static int pinLoc(int x, int viewMax, int docMax) { 2069 // Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax); 2070 if (docMax < viewMax) { // the doc has room on the sides for "blank" 2071 // pin the short document to the top/left of the screen 2072 x = 0; 2073 // Log.d(LOGTAG, "--- center " + x); 2074 } else if (x < 0) { 2075 x = 0; 2076 // Log.d(LOGTAG, "--- zero"); 2077 } else if (x + viewMax > docMax) { 2078 x = docMax - viewMax; 2079 // Log.d(LOGTAG, "--- pin " + x); 2080 } 2081 return x; 2082 } 2083 2084 // Expects x in view coordinates 2085 private int pinLocX(int x) { 2086 if (mInOverScrollMode) return x; 2087 return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange()); 2088 } 2089 2090 // Expects y in view coordinates 2091 private int pinLocY(int y) { 2092 if (mInOverScrollMode) return y; 2093 return pinLoc(y, getViewHeightWithTitle(), 2094 computeRealVerticalScrollRange() + getTitleHeight()); 2095 } 2096 2097 /** 2098 * A title bar which is embedded in this WebView, and scrolls along with it 2099 * vertically, but not horizontally. 2100 */ 2101 private View mTitleBar; 2102 2103 /** 2104 * Since we draw the title bar ourselves, we removed the shadow from the 2105 * browser's activity. We do want a shadow at the bottom of the title bar, 2106 * or at the top of the screen if the title bar is not visible. This 2107 * drawable serves that purpose. 2108 */ 2109 private Drawable mTitleShadow; 2110 2111 /** 2112 * Add or remove a title bar to be embedded into the WebView, and scroll 2113 * along with it vertically, while remaining in view horizontally. Pass 2114 * null to remove the title bar from the WebView, and return to drawing 2115 * the WebView normally without translating to account for the title bar. 2116 * @hide 2117 */ 2118 public void setEmbeddedTitleBar(View v) { 2119 if (mTitleBar == v) return; 2120 if (mTitleBar != null) { 2121 removeView(mTitleBar); 2122 } 2123 if (null != v) { 2124 addView(v, new AbsoluteLayout.LayoutParams( 2125 ViewGroup.LayoutParams.MATCH_PARENT, 2126 ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0)); 2127 if (mTitleShadow == null) { 2128 mTitleShadow = (Drawable) mContext.getResources().getDrawable( 2129 com.android.internal.R.drawable.title_bar_shadow); 2130 } 2131 } 2132 mTitleBar = v; 2133 } 2134 2135 /** 2136 * Given a distance in view space, convert it to content space. Note: this 2137 * does not reflect translation, just scaling, so this should not be called 2138 * with coordinates, but should be called for dimensions like width or 2139 * height. 2140 */ 2141 private int viewToContentDimension(int d) { 2142 return Math.round(d * mInvActualScale); 2143 } 2144 2145 /** 2146 * Given an x coordinate in view space, convert it to content space. Also 2147 * may be used for absolute heights (such as for the WebTextView's 2148 * textSize, which is unaffected by the height of the title bar). 2149 */ 2150 /*package*/ int viewToContentX(int x) { 2151 return viewToContentDimension(x); 2152 } 2153 2154 /** 2155 * Given a y coordinate in view space, convert it to content space. 2156 * Takes into account the height of the title bar if there is one 2157 * embedded into the WebView. 2158 */ 2159 /*package*/ int viewToContentY(int y) { 2160 return viewToContentDimension(y - getTitleHeight()); 2161 } 2162 2163 /** 2164 * Given a x coordinate in view space, convert it to content space. 2165 * Returns the result as a float. 2166 */ 2167 private float viewToContentXf(int x) { 2168 return x * mInvActualScale; 2169 } 2170 2171 /** 2172 * Given a y coordinate in view space, convert it to content space. 2173 * Takes into account the height of the title bar if there is one 2174 * embedded into the WebView. Returns the result as a float. 2175 */ 2176 private float viewToContentYf(int y) { 2177 return (y - getTitleHeight()) * mInvActualScale; 2178 } 2179 2180 /** 2181 * Given a distance in content space, convert it to view space. Note: this 2182 * does not reflect translation, just scaling, so this should not be called 2183 * with coordinates, but should be called for dimensions like width or 2184 * height. 2185 */ 2186 /*package*/ int contentToViewDimension(int d) { 2187 return Math.round(d * mActualScale); 2188 } 2189 2190 /** 2191 * Given an x coordinate in content space, convert it to view 2192 * space. 2193 */ 2194 /*package*/ int contentToViewX(int x) { 2195 return contentToViewDimension(x); 2196 } 2197 2198 /** 2199 * Given a y coordinate in content space, convert it to view 2200 * space. Takes into account the height of the title bar. 2201 */ 2202 /*package*/ int contentToViewY(int y) { 2203 return contentToViewDimension(y) + getTitleHeight(); 2204 } 2205 2206 private Rect contentToViewRect(Rect x) { 2207 return new Rect(contentToViewX(x.left), contentToViewY(x.top), 2208 contentToViewX(x.right), contentToViewY(x.bottom)); 2209 } 2210 2211 /* To invalidate a rectangle in content coordinates, we need to transform 2212 the rect into view coordinates, so we can then call invalidate(...). 2213 2214 Normally, we would just call contentToView[XY](...), which eventually 2215 calls Math.round(coordinate * mActualScale). However, for invalidates, 2216 we need to account for the slop that occurs with antialiasing. To 2217 address that, we are a little more liberal in the size of the rect that 2218 we invalidate. 2219 2220 This liberal calculation calls floor() for the top/left, and ceil() for 2221 the bottom/right coordinates. This catches the possible extra pixels of 2222 antialiasing that we might have missed with just round(). 2223 */ 2224 2225 // Called by JNI to invalidate the View, given rectangle coordinates in 2226 // content space 2227 private void viewInvalidate(int l, int t, int r, int b) { 2228 final float scale = mActualScale; 2229 final int dy = getTitleHeight(); 2230 invalidate((int)Math.floor(l * scale), 2231 (int)Math.floor(t * scale) + dy, 2232 (int)Math.ceil(r * scale), 2233 (int)Math.ceil(b * scale) + dy); 2234 } 2235 2236 // Called by JNI to invalidate the View after a delay, given rectangle 2237 // coordinates in content space 2238 private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) { 2239 final float scale = mActualScale; 2240 final int dy = getTitleHeight(); 2241 postInvalidateDelayed(delay, 2242 (int)Math.floor(l * scale), 2243 (int)Math.floor(t * scale) + dy, 2244 (int)Math.ceil(r * scale), 2245 (int)Math.ceil(b * scale) + dy); 2246 } 2247 2248 private void invalidateContentRect(Rect r) { 2249 viewInvalidate(r.left, r.top, r.right, r.bottom); 2250 } 2251 2252 // stop the scroll animation, and don't let a subsequent fling add 2253 // to the existing velocity 2254 private void abortAnimation() { 2255 mScroller.abortAnimation(); 2256 mLastVelocity = 0; 2257 } 2258 2259 /* call from webcoreview.draw(), so we're still executing in the UI thread 2260 */ 2261 private void recordNewContentSize(int w, int h, boolean updateLayout) { 2262 2263 // premature data from webkit, ignore 2264 if ((w | h) == 0) { 2265 return; 2266 } 2267 2268 // don't abort a scroll animation if we didn't change anything 2269 if (mContentWidth != w || mContentHeight != h) { 2270 // record new dimensions 2271 mContentWidth = w; 2272 mContentHeight = h; 2273 // If history Picture is drawn, don't update scroll. They will be 2274 // updated when we get out of that mode. 2275 if (!mDrawHistory) { 2276 // repin our scroll, taking into account the new content size 2277 int oldX = mScrollX; 2278 int oldY = mScrollY; 2279 mScrollX = pinLocX(mScrollX); 2280 mScrollY = pinLocY(mScrollY); 2281 if (oldX != mScrollX || oldY != mScrollY) { 2282 onScrollChanged(mScrollX, mScrollY, oldX, oldY); 2283 } 2284 if (!mScroller.isFinished()) { 2285 // We are in the middle of a scroll. Repin the final scroll 2286 // position. 2287 mScroller.setFinalX(pinLocX(mScroller.getFinalX())); 2288 mScroller.setFinalY(pinLocY(mScroller.getFinalY())); 2289 } 2290 } 2291 } 2292 contentSizeChanged(updateLayout); 2293 } 2294 2295 private void setNewZoomScale(float scale, boolean updateTextWrapScale, 2296 boolean force) { 2297 if (scale < mMinZoomScale) { 2298 scale = mMinZoomScale; 2299 // set mInZoomOverview for non mobile sites 2300 if (scale < mDefaultScale) mInZoomOverview = true; 2301 } else if (scale > mMaxZoomScale) { 2302 scale = mMaxZoomScale; 2303 } 2304 if (updateTextWrapScale) { 2305 mTextWrapScale = scale; 2306 // reset mLastHeightSent to force VIEW_SIZE_CHANGED sent to WebKit 2307 mLastHeightSent = 0; 2308 } 2309 if (scale != mActualScale || force) { 2310 if (mDrawHistory) { 2311 // If history Picture is drawn, don't update scroll. They will 2312 // be updated when we get out of that mode. 2313 if (scale != mActualScale && !mPreviewZoomOnly) { 2314 mCallbackProxy.onScaleChanged(mActualScale, scale); 2315 } 2316 mActualScale = scale; 2317 mInvActualScale = 1 / scale; 2318 sendViewSizeZoom(); 2319 } else { 2320 // update our scroll so we don't appear to jump 2321 // i.e. keep the center of the doc in the center of the view 2322 2323 int oldX = mScrollX; 2324 int oldY = mScrollY; 2325 float ratio = scale * mInvActualScale; // old inverse 2326 float sx = ratio * oldX + (ratio - 1) * mZoomCenterX; 2327 float sy = ratio * oldY + (ratio - 1) 2328 * (mZoomCenterY - getTitleHeight()); 2329 2330 // now update our new scale and inverse 2331 if (scale != mActualScale && !mPreviewZoomOnly) { 2332 mCallbackProxy.onScaleChanged(mActualScale, scale); 2333 } 2334 mActualScale = scale; 2335 mInvActualScale = 1 / scale; 2336 2337 // Scale all the child views 2338 mViewManager.scaleAll(); 2339 2340 // as we don't have animation for scaling, don't do animation 2341 // for scrolling, as it causes weird intermediate state 2342 // pinScrollTo(Math.round(sx), Math.round(sy)); 2343 mScrollX = pinLocX(Math.round(sx)); 2344 mScrollY = pinLocY(Math.round(sy)); 2345 2346 // update webkit 2347 if (oldX != mScrollX || oldY != mScrollY) { 2348 onScrollChanged(mScrollX, mScrollY, oldX, oldY); 2349 } else { 2350 // the scroll position is adjusted at the beginning of the 2351 // zoom animation. But we want to update the WebKit at the 2352 // end of the zoom animation. See comments in onScaleEnd(). 2353 sendOurVisibleRect(); 2354 } 2355 sendViewSizeZoom(); 2356 } 2357 } 2358 } 2359 2360 // Used to avoid sending many visible rect messages. 2361 private Rect mLastVisibleRectSent; 2362 private Rect mLastGlobalRect; 2363 2364 private Rect sendOurVisibleRect() { 2365 if (mPreviewZoomOnly) return mLastVisibleRectSent; 2366 2367 Rect rect = new Rect(); 2368 calcOurContentVisibleRect(rect); 2369 // Rect.equals() checks for null input. 2370 if (!rect.equals(mLastVisibleRectSent)) { 2371 Point pos = new Point(rect.left, rect.top); 2372 mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET, 2373 nativeMoveGeneration(), 0, pos); 2374 mLastVisibleRectSent = rect; 2375 } 2376 Rect globalRect = new Rect(); 2377 if (getGlobalVisibleRect(globalRect) 2378 && !globalRect.equals(mLastGlobalRect)) { 2379 if (DebugFlags.WEB_VIEW) { 2380 Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + "," 2381 + globalRect.top + ",r=" + globalRect.right + ",b=" 2382 + globalRect.bottom); 2383 } 2384 // TODO: the global offset is only used by windowRect() 2385 // in ChromeClientAndroid ; other clients such as touch 2386 // and mouse events could return view + screen relative points. 2387 mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect); 2388 mLastGlobalRect = globalRect; 2389 } 2390 return rect; 2391 } 2392 2393 // Sets r to be the visible rectangle of our webview in view coordinates 2394 private void calcOurVisibleRect(Rect r) { 2395 Point p = new Point(); 2396 getGlobalVisibleRect(r, p); 2397 r.offset(-p.x, -p.y); 2398 if (mFindIsUp) { 2399 r.bottom -= mFindHeight; 2400 } 2401 } 2402 2403 // Sets r to be our visible rectangle in content coordinates 2404 private void calcOurContentVisibleRect(Rect r) { 2405 calcOurVisibleRect(r); 2406 // since we might overscroll, pin the rect to the bounds of the content 2407 r.left = Math.max(viewToContentX(r.left), 0); 2408 // viewToContentY will remove the total height of the title bar. Add 2409 // the visible height back in to account for the fact that if the title 2410 // bar is partially visible, the part of the visible rect which is 2411 // displaying our content is displaced by that amount. 2412 r.top = Math.max(viewToContentY(r.top + getVisibleTitleHeight()), 0); 2413 r.right = Math.min(viewToContentX(r.right), mContentWidth); 2414 r.bottom = Math.min(viewToContentY(r.bottom), mContentHeight); 2415 } 2416 2417 // Sets r to be our visible rectangle in content coordinates. We use this 2418 // method on the native side to compute the position of the fixed layers. 2419 // Uses floating coordinates (necessary to correctly place elements when 2420 // the scale factor is not 1) 2421 private void calcOurContentVisibleRectF(RectF r) { 2422 Rect ri = new Rect(0,0,0,0); 2423 calcOurVisibleRect(ri); 2424 // pin the rect to the bounds of the content 2425 r.left = Math.max(viewToContentXf(ri.left), 0.0f); 2426 // viewToContentY will remove the total height of the title bar. Add 2427 // the visible height back in to account for the fact that if the title 2428 // bar is partially visible, the part of the visible rect which is 2429 // displaying our content is displaced by that amount. 2430 r.top = Math.max(viewToContentYf(ri.top + getVisibleTitleHeight()), 0.0f); 2431 r.right = Math.min(viewToContentXf(ri.right), (float)mContentWidth); 2432 r.bottom = Math.min(viewToContentYf(ri.bottom), (float)mContentHeight); 2433 } 2434 2435 static class ViewSizeData { 2436 int mWidth; 2437 int mHeight; 2438 int mTextWrapWidth; 2439 int mAnchorX; 2440 int mAnchorY; 2441 float mScale; 2442 boolean mIgnoreHeight; 2443 } 2444 2445 /** 2446 * Compute unzoomed width and height, and if they differ from the last 2447 * values we sent, send them to webkit (to be used has new viewport) 2448 * 2449 * @return true if new values were sent 2450 */ 2451 private boolean sendViewSizeZoom() { 2452 if (mPreviewZoomOnly) return false; 2453 2454 int viewWidth = getViewWidth(); 2455 int newWidth = Math.round(viewWidth * mInvActualScale); 2456 int newHeight = Math.round(getViewHeight() * mInvActualScale); 2457 /* 2458 * Because the native side may have already done a layout before the 2459 * View system was able to measure us, we have to send a height of 0 to 2460 * remove excess whitespace when we grow our width. This will trigger a 2461 * layout and a change in content size. This content size change will 2462 * mean that contentSizeChanged will either call this method directly or 2463 * indirectly from onSizeChanged. 2464 */ 2465 if (newWidth > mLastWidthSent && mWrapContent) { 2466 newHeight = 0; 2467 } 2468 // Avoid sending another message if the dimensions have not changed. 2469 if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { 2470 ViewSizeData data = new ViewSizeData(); 2471 data.mWidth = newWidth; 2472 data.mHeight = newHeight; 2473 data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);; 2474 data.mScale = mActualScale; 2475 data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure; 2476 data.mAnchorX = mAnchorX; 2477 data.mAnchorY = mAnchorY; 2478 mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data); 2479 mLastWidthSent = newWidth; 2480 mLastHeightSent = newHeight; 2481 mAnchorX = mAnchorY = 0; 2482 return true; 2483 } 2484 return false; 2485 } 2486 2487 private int computeRealHorizontalScrollRange() { 2488 if (mDrawHistory) { 2489 return mHistoryWidth; 2490 } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF 2491 && (mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) { 2492 // only honor the scrollbar mode when it is at minimum zoom level 2493 return computeHorizontalScrollExtent(); 2494 } else { 2495 // to avoid rounding error caused unnecessary scrollbar, use floor 2496 return (int) Math.floor(mContentWidth * mActualScale); 2497 } 2498 } 2499 2500 @Override 2501 protected int computeHorizontalScrollRange() { 2502 int range = computeRealHorizontalScrollRange(); 2503 2504 // Adjust reported range if overscrolled to compress the scroll bars 2505 final int scrollX = mScrollX; 2506 final int overscrollRight = computeMaxScrollX(); 2507 if (scrollX < 0) { 2508 range -= scrollX; 2509 } else if (scrollX > overscrollRight) { 2510 range += scrollX - overscrollRight; 2511 } 2512 2513 return range; 2514 } 2515 2516 @Override 2517 protected int computeHorizontalScrollOffset() { 2518 return Math.max(mScrollX, 0); 2519 } 2520 2521 private int computeRealVerticalScrollRange() { 2522 if (mDrawHistory) { 2523 return mHistoryHeight; 2524 } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF 2525 && (mActualScale - mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) { 2526 // only honor the scrollbar mode when it is at minimum zoom level 2527 return computeVerticalScrollExtent(); 2528 } else { 2529 // to avoid rounding error caused unnecessary scrollbar, use floor 2530 return (int) Math.floor(mContentHeight * mActualScale); 2531 } 2532 } 2533 2534 @Override 2535 protected int computeVerticalScrollRange() { 2536 int range = computeRealVerticalScrollRange(); 2537 2538 // Adjust reported range if overscrolled to compress the scroll bars 2539 final int scrollY = mScrollY; 2540 final int overscrollBottom = computeMaxScrollY(); 2541 if (scrollY < 0) { 2542 range -= scrollY; 2543 } else if (scrollY > overscrollBottom) { 2544 range += scrollY - overscrollBottom; 2545 } 2546 2547 return range; 2548 } 2549 2550 @Override 2551 protected int computeVerticalScrollOffset() { 2552 return Math.max(mScrollY - getTitleHeight(), 0); 2553 } 2554 2555 @Override 2556 protected int computeVerticalScrollExtent() { 2557 return getViewHeight(); 2558 } 2559 2560 /** @hide */ 2561 @Override 2562 protected void onDrawVerticalScrollBar(Canvas canvas, 2563 Drawable scrollBar, 2564 int l, int t, int r, int b) { 2565 if (mScrollY < 0) { 2566 t -= mScrollY; 2567 } 2568 scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b); 2569 scrollBar.draw(canvas); 2570 } 2571 2572 @Override 2573 protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, 2574 boolean clampedY) { 2575 mInOverScrollMode = false; 2576 int maxX = computeMaxScrollX(); 2577 int maxY = computeMaxScrollY(); 2578 if (maxX == 0) { 2579 // do not over scroll x if the page just fits the screen 2580 scrollX = pinLocX(scrollX); 2581 } else if (scrollX < 0 || scrollX > maxX) { 2582 mInOverScrollMode = true; 2583 } 2584 if (scrollY < 0 || scrollY > maxY) { 2585 mInOverScrollMode = true; 2586 } 2587 2588 int oldX = mScrollX; 2589 int oldY = mScrollY; 2590 2591 super.scrollTo(scrollX, scrollY); 2592 2593 // Only show overscroll bars if there was no movement in any direction 2594 // as a result of scrolling. 2595 if (mEdgeGlowTop != null && oldY == mScrollY && oldX == mScrollX) { 2596 // Don't show left/right glows if we fit the whole content. 2597 // Also don't show if there was vertical movement. 2598 if (maxX > 0) { 2599 final int pulledToX = oldX + mOverscrollDeltaX; 2600 if (pulledToX < 0) { 2601 mEdgeGlowLeft.onPull((float) mOverscrollDeltaX / getWidth()); 2602 if (!mEdgeGlowRight.isFinished()) { 2603 mEdgeGlowRight.onRelease(); 2604 } 2605 } else if (pulledToX > maxX) { 2606 mEdgeGlowRight.onPull((float) mOverscrollDeltaX / getWidth()); 2607 if (!mEdgeGlowLeft.isFinished()) { 2608 mEdgeGlowLeft.onRelease(); 2609 } 2610 } 2611 mOverscrollDeltaX = 0; 2612 } 2613 2614 if (maxY > 0 || getOverScrollMode() == OVER_SCROLL_ALWAYS) { 2615 final int pulledToY = oldY + mOverscrollDeltaY; 2616 if (pulledToY < 0) { 2617 mEdgeGlowTop.onPull((float) mOverscrollDeltaY / getHeight()); 2618 if (!mEdgeGlowBottom.isFinished()) { 2619 mEdgeGlowBottom.onRelease(); 2620 } 2621 } else if (pulledToY > maxY) { 2622 mEdgeGlowBottom.onPull((float) mOverscrollDeltaY / getHeight()); 2623 if (!mEdgeGlowTop.isFinished()) { 2624 mEdgeGlowTop.onRelease(); 2625 } 2626 } 2627 mOverscrollDeltaY = 0; 2628 } 2629 } 2630 } 2631 2632 /** 2633 * Get the url for the current page. This is not always the same as the url 2634 * passed to WebViewClient.onPageStarted because although the load for 2635 * that url has begun, the current page may not have changed. 2636 * @return The url for the current page. 2637 */ 2638 public String getUrl() { 2639 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2640 return h != null ? h.getUrl() : null; 2641 } 2642 2643 /** 2644 * Get the original url for the current page. This is not always the same 2645 * as the url passed to WebViewClient.onPageStarted because although the 2646 * load for that url has begun, the current page may not have changed. 2647 * Also, there may have been redirects resulting in a different url to that 2648 * originally requested. 2649 * @return The url that was originally requested for the current page. 2650 */ 2651 public String getOriginalUrl() { 2652 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2653 return h != null ? h.getOriginalUrl() : null; 2654 } 2655 2656 /** 2657 * Get the title for the current page. This is the title of the current page 2658 * until WebViewClient.onReceivedTitle is called. 2659 * @return The title for the current page. 2660 */ 2661 public String getTitle() { 2662 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2663 return h != null ? h.getTitle() : null; 2664 } 2665 2666 /** 2667 * Get the favicon for the current page. This is the favicon of the current 2668 * page until WebViewClient.onReceivedIcon is called. 2669 * @return The favicon for the current page. 2670 */ 2671 public Bitmap getFavicon() { 2672 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2673 return h != null ? h.getFavicon() : null; 2674 } 2675 2676 /** 2677 * Get the touch icon url for the apple-touch-icon <link> element. 2678 * @hide 2679 */ 2680 public String getTouchIconUrl() { 2681 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2682 return h != null ? h.getTouchIconUrl() : null; 2683 } 2684 2685 /** 2686 * Get the progress for the current page. 2687 * @return The progress for the current page between 0 and 100. 2688 */ 2689 public int getProgress() { 2690 return mCallbackProxy.getProgress(); 2691 } 2692 2693 /** 2694 * @return the height of the HTML content. 2695 */ 2696 public int getContentHeight() { 2697 return mContentHeight; 2698 } 2699 2700 /** 2701 * @return the width of the HTML content. 2702 * @hide 2703 */ 2704 public int getContentWidth() { 2705 return mContentWidth; 2706 } 2707 2708 /** 2709 * Pause all layout, parsing, and javascript timers for all webviews. This 2710 * is a global requests, not restricted to just this webview. This can be 2711 * useful if the application has been paused. 2712 */ 2713 public void pauseTimers() { 2714 mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); 2715 } 2716 2717 /** 2718 * Resume all layout, parsing, and javascript timers for all webviews. 2719 * This will resume dispatching all timers. 2720 */ 2721 public void resumeTimers() { 2722 mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); 2723 } 2724 2725 /** 2726 * Call this to pause any extra processing associated with this view and 2727 * its associated DOM/plugins/javascript/etc. For example, if the view is 2728 * taken offscreen, this could be called to reduce unnecessary CPU and/or 2729 * network traffic. When the view is again "active", call onResume(). 2730 * 2731 * Note that this differs from pauseTimers(), which affects all views/DOMs 2732 * @hide 2733 */ 2734 public void onPause() { 2735 if (!mIsPaused) { 2736 mIsPaused = true; 2737 mWebViewCore.sendMessage(EventHub.ON_PAUSE); 2738 } 2739 } 2740 2741 /** 2742 * Call this to balanace a previous call to onPause() 2743 * @hide 2744 */ 2745 public void onResume() { 2746 if (mIsPaused) { 2747 mIsPaused = false; 2748 mWebViewCore.sendMessage(EventHub.ON_RESUME); 2749 } 2750 } 2751 2752 /** 2753 * Returns true if the view is paused, meaning onPause() was called. Calling 2754 * onResume() sets the paused state back to false. 2755 * @hide 2756 */ 2757 public boolean isPaused() { 2758 return mIsPaused; 2759 } 2760 2761 /** 2762 * Call this to inform the view that memory is low so that it can 2763 * free any available memory. 2764 */ 2765 public void freeMemory() { 2766 mWebViewCore.sendMessage(EventHub.FREE_MEMORY); 2767 } 2768 2769 /** 2770 * Clear the resource cache. Note that the cache is per-application, so 2771 * this will clear the cache for all WebViews used. 2772 * 2773 * @param includeDiskFiles If false, only the RAM cache is cleared. 2774 */ 2775 public void clearCache(boolean includeDiskFiles) { 2776 // Note: this really needs to be a static method as it clears cache for all 2777 // WebView. But we need mWebViewCore to send message to WebCore thread, so 2778 // we can't make this static. 2779 mWebViewCore.sendMessage(EventHub.CLEAR_CACHE, 2780 includeDiskFiles ? 1 : 0, 0); 2781 } 2782 2783 /** 2784 * Make sure that clearing the form data removes the adapter from the 2785 * currently focused textfield if there is one. 2786 */ 2787 public void clearFormData() { 2788 if (inEditingMode()) { 2789 AutoCompleteAdapter adapter = null; 2790 mWebTextView.setAdapterCustom(adapter); 2791 } 2792 } 2793 2794 /** 2795 * Tell the WebView to clear its internal back/forward list. 2796 */ 2797 public void clearHistory() { 2798 mCallbackProxy.getBackForwardList().setClearPending(); 2799 mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY); 2800 } 2801 2802 /** 2803 * Clear the SSL preferences table stored in response to proceeding with SSL 2804 * certificate errors. 2805 */ 2806 public void clearSslPreferences() { 2807 mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); 2808 } 2809 2810 /** 2811 * Return the WebBackForwardList for this WebView. This contains the 2812 * back/forward list for use in querying each item in the history stack. 2813 * This is a copy of the private WebBackForwardList so it contains only a 2814 * snapshot of the current state. Multiple calls to this method may return 2815 * different objects. The object returned from this method will not be 2816 * updated to reflect any new state. 2817 */ 2818 public WebBackForwardList copyBackForwardList() { 2819 return mCallbackProxy.getBackForwardList().clone(); 2820 } 2821 2822 /* 2823 * Highlight and scroll to the next occurance of String in findAll. 2824 * Wraps the page infinitely, and scrolls. Must be called after 2825 * calling findAll. 2826 * 2827 * @param forward Direction to search. 2828 */ 2829 public void findNext(boolean forward) { 2830 if (0 == mNativeClass) return; // client isn't initialized 2831 nativeFindNext(forward); 2832 } 2833 2834 /* 2835 * Find all instances of find on the page and highlight them. 2836 * @param find String to find. 2837 * @return int The number of occurances of the String "find" 2838 * that were found. 2839 */ 2840 public int findAll(String find) { 2841 if (0 == mNativeClass) return 0; // client isn't initialized 2842 int result = find != null ? nativeFindAll(find.toLowerCase(), 2843 find.toUpperCase()) : 0; 2844 invalidate(); 2845 mLastFind = find; 2846 return result; 2847 } 2848 2849 /** 2850 * @hide 2851 */ 2852 public void setFindIsUp(boolean isUp) { 2853 mFindIsUp = isUp; 2854 if (isUp) { 2855 recordNewContentSize(mContentWidth, mContentHeight + mFindHeight, 2856 false); 2857 } 2858 if (0 == mNativeClass) return; // client isn't initialized 2859 nativeSetFindIsUp(isUp); 2860 } 2861 2862 /** 2863 * @hide 2864 */ 2865 public int findIndex() { 2866 if (0 == mNativeClass) return -1; 2867 return nativeFindIndex(); 2868 } 2869 2870 // Used to know whether the find dialog is open. Affects whether 2871 // or not we draw the highlights for matches. 2872 private boolean mFindIsUp; 2873 2874 private int mFindHeight; 2875 // Keep track of the last string sent, so we can search again after an 2876 // orientation change or the dismissal of the soft keyboard. 2877 private String mLastFind; 2878 2879 /** 2880 * Return the first substring consisting of the address of a physical 2881 * location. Currently, only addresses in the United States are detected, 2882 * and consist of: 2883 * - a house number 2884 * - a street name 2885 * - a street type (Road, Circle, etc), either spelled out or abbreviated 2886 * - a city name 2887 * - a state or territory, either spelled out or two-letter abbr. 2888 * - an optional 5 digit or 9 digit zip code. 2889 * 2890 * All names must be correctly capitalized, and the zip code, if present, 2891 * must be valid for the state. The street type must be a standard USPS 2892 * spelling or abbreviation. The state or territory must also be spelled 2893 * or abbreviated using USPS standards. The house number may not exceed 2894 * five digits. 2895 * @param addr The string to search for addresses. 2896 * 2897 * @return the address, or if no address is found, return null. 2898 */ 2899 public static String findAddress(String addr) { 2900 return findAddress(addr, false); 2901 } 2902 2903 /** 2904 * @hide 2905 * Return the first substring consisting of the address of a physical 2906 * location. Currently, only addresses in the United States are detected, 2907 * and consist of: 2908 * - a house number 2909 * - a street name 2910 * - a street type (Road, Circle, etc), either spelled out or abbreviated 2911 * - a city name 2912 * - a state or territory, either spelled out or two-letter abbr. 2913 * - an optional 5 digit or 9 digit zip code. 2914 * 2915 * Names are optionally capitalized, and the zip code, if present, 2916 * must be valid for the state. The street type must be a standard USPS 2917 * spelling or abbreviation. The state or territory must also be spelled 2918 * or abbreviated using USPS standards. The house number may not exceed 2919 * five digits. 2920 * @param addr The string to search for addresses. 2921 * @param caseInsensitive addr Set to true to make search ignore case. 2922 * 2923 * @return the address, or if no address is found, return null. 2924 */ 2925 public static String findAddress(String addr, boolean caseInsensitive) { 2926 return WebViewCore.nativeFindAddress(addr, caseInsensitive); 2927 } 2928 2929 /* 2930 * Clear the highlighting surrounding text matches created by findAll. 2931 */ 2932 public void clearMatches() { 2933 mLastFind = ""; 2934 if (mNativeClass == 0) 2935 return; 2936 nativeSetFindIsEmpty(); 2937 invalidate(); 2938 } 2939 2940 /** 2941 * @hide 2942 */ 2943 public void notifyFindDialogDismissed() { 2944 if (mWebViewCore == null) { 2945 return; 2946 } 2947 clearMatches(); 2948 setFindIsUp(false); 2949 recordNewContentSize(mContentWidth, mContentHeight - mFindHeight, 2950 false); 2951 // Now that the dialog has been removed, ensure that we scroll to a 2952 // location that is not beyond the end of the page. 2953 pinScrollTo(mScrollX, mScrollY, false, 0); 2954 invalidate(); 2955 } 2956 2957 /** 2958 * @hide 2959 */ 2960 public void setFindDialogHeight(int height) { 2961 if (DebugFlags.WEB_VIEW) { 2962 Log.v(LOGTAG, "setFindDialogHeight height=" + height); 2963 } 2964 mFindHeight = height; 2965 } 2966 2967 /** 2968 * Query the document to see if it contains any image references. The 2969 * message object will be dispatched with arg1 being set to 1 if images 2970 * were found and 0 if the document does not reference any images. 2971 * @param response The message that will be dispatched with the result. 2972 */ 2973 public void documentHasImages(Message response) { 2974 if (response == null) { 2975 return; 2976 } 2977 mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response); 2978 } 2979 2980 @Override 2981 public void computeScroll() { 2982 if (mScroller.computeScrollOffset()) { 2983 int oldX = mScrollX; 2984 int oldY = mScrollY; 2985 int x = mScroller.getCurrX(); 2986 int y = mScroller.getCurrY(); 2987 invalidate(); // So we draw again 2988 2989 if (oldX != x || oldY != y) { 2990 final int rangeX = computeMaxScrollX(); 2991 final int rangeY = computeMaxScrollY(); 2992 overScrollBy(x - oldX, y - oldY, oldX, oldY, 2993 rangeX, rangeY, 2994 mOverflingDistance, mOverflingDistance, false); 2995 2996 if (mEdgeGlowTop != null) { 2997 if (rangeY > 0 || getOverScrollMode() == OVER_SCROLL_ALWAYS) { 2998 if (y < 0 && oldY >= 0) { 2999 mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); 3000 if (!mEdgeGlowBottom.isFinished()) { 3001 mEdgeGlowBottom.onRelease(); 3002 } 3003 } else if (y > rangeY && oldY <= rangeY) { 3004 mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); 3005 if (!mEdgeGlowTop.isFinished()) { 3006 mEdgeGlowTop.onRelease(); 3007 } 3008 } 3009 } 3010 3011 if (rangeX > 0) { 3012 if (x < 0 && oldX >= 0) { 3013 mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); 3014 if (!mEdgeGlowRight.isFinished()) { 3015 mEdgeGlowRight.onRelease(); 3016 } 3017 } else if (x > rangeX && oldX <= rangeX) { 3018 mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); 3019 if (!mEdgeGlowLeft.isFinished()) { 3020 mEdgeGlowLeft.onRelease(); 3021 } 3022 } 3023 } 3024 } 3025 } 3026 if (mScroller.isFinished()) { 3027 mPrivateHandler.sendEmptyMessage(RESUME_WEBCORE_PRIORITY); 3028 } 3029 } else { 3030 super.computeScroll(); 3031 } 3032 } 3033 3034 private static int computeDuration(int dx, int dy) { 3035 int distance = Math.max(Math.abs(dx), Math.abs(dy)); 3036 int duration = distance * 1000 / STD_SPEED; 3037 return Math.min(duration, MAX_DURATION); 3038 } 3039 3040 // helper to pin the scrollBy parameters (already in view coordinates) 3041 // returns true if the scroll was changed 3042 private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) { 3043 return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration); 3044 } 3045 // helper to pin the scrollTo parameters (already in view coordinates) 3046 // returns true if the scroll was changed 3047 private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) { 3048 x = pinLocX(x); 3049 y = pinLocY(y); 3050 int dx = x - mScrollX; 3051 int dy = y - mScrollY; 3052 3053 if ((dx | dy) == 0) { 3054 return false; 3055 } 3056 if (animate) { 3057 // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); 3058 mScroller.startScroll(mScrollX, mScrollY, dx, dy, 3059 animationDuration > 0 ? animationDuration : computeDuration(dx, dy)); 3060 awakenScrollBars(mScroller.getDuration()); 3061 invalidate(); 3062 } else { 3063 abortAnimation(); // just in case 3064 scrollTo(x, y); 3065 } 3066 return true; 3067 } 3068 3069 // Scale from content to view coordinates, and pin. 3070 // Also called by jni webview.cpp 3071 private boolean setContentScrollBy(int cx, int cy, boolean animate) { 3072 if (mDrawHistory) { 3073 // disallow WebView to change the scroll position as History Picture 3074 // is used in the view system. 3075 // TODO: as we switchOutDrawHistory when trackball or navigation 3076 // keys are hit, this should be safe. Right? 3077 return false; 3078 } 3079 cx = contentToViewDimension(cx); 3080 cy = contentToViewDimension(cy); 3081 if (mHeightCanMeasure) { 3082 // move our visible rect according to scroll request 3083 if (cy != 0) { 3084 Rect tempRect = new Rect(); 3085 calcOurVisibleRect(tempRect); 3086 tempRect.offset(cx, cy); 3087 requestRectangleOnScreen(tempRect); 3088 } 3089 // FIXME: We scroll horizontally no matter what because currently 3090 // ScrollView and ListView will not scroll horizontally. 3091 // FIXME: Why do we only scroll horizontally if there is no 3092 // vertical scroll? 3093 // Log.d(LOGTAG, "setContentScrollBy cy=" + cy); 3094 return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0); 3095 } else { 3096 return pinScrollBy(cx, cy, animate, 0); 3097 } 3098 } 3099 3100 /** 3101 * Called by CallbackProxy when the page finishes loading. 3102 * @param url The URL of the page which has finished loading. 3103 */ 3104 /* package */ void onPageFinished(String url) { 3105 if (mPageThatNeedsToSlideTitleBarOffScreen != null) { 3106 // If the user is now on a different page, or has scrolled the page 3107 // past the point where the title bar is offscreen, ignore the 3108 // scroll request. 3109 if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url) 3110 && mScrollX == 0 && mScrollY == 0) { 3111 pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true, 3112 SLIDE_TITLE_DURATION); 3113 } 3114 mPageThatNeedsToSlideTitleBarOffScreen = null; 3115 } 3116 } 3117 3118 /** 3119 * The URL of a page that sent a message to scroll the title bar off screen. 3120 * 3121 * Many mobile sites tell the page to scroll to (0,1) in order to scroll the 3122 * title bar off the screen. Sometimes, the scroll position is set before 3123 * the page finishes loading. Rather than scrolling while the page is still 3124 * loading, keep track of the URL and new scroll position so we can perform 3125 * the scroll once the page finishes loading. 3126 */ 3127 private String mPageThatNeedsToSlideTitleBarOffScreen; 3128 3129 /** 3130 * The destination Y scroll position to be used when the page finishes 3131 * loading. See mPageThatNeedsToSlideTitleBarOffScreen. 3132 */ 3133 private int mYDistanceToSlideTitleOffScreen; 3134 3135 // scale from content to view coordinates, and pin 3136 // return true if pin caused the final x/y different than the request cx/cy, 3137 // and a future scroll may reach the request cx/cy after our size has 3138 // changed 3139 // return false if the view scroll to the exact position as it is requested, 3140 // where negative numbers are taken to mean 0 3141 private boolean setContentScrollTo(int cx, int cy) { 3142 if (mDrawHistory) { 3143 // disallow WebView to change the scroll position as History Picture 3144 // is used in the view system. 3145 // One known case where this is called is that WebCore tries to 3146 // restore the scroll position. As history Picture already uses the 3147 // saved scroll position, it is ok to skip this. 3148 return false; 3149 } 3150 int vx; 3151 int vy; 3152 if ((cx | cy) == 0) { 3153 // If the page is being scrolled to (0,0), do not add in the title 3154 // bar's height, and simply scroll to (0,0). (The only other work 3155 // in contentToView_ is to multiply, so this would not change 0.) 3156 vx = 0; 3157 vy = 0; 3158 } else { 3159 vx = contentToViewX(cx); 3160 vy = contentToViewY(cy); 3161 } 3162 // Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" + 3163 // vx + " " + vy + "]"); 3164 // Some mobile sites attempt to scroll the title bar off the page by 3165 // scrolling to (0,1). If we are at the top left corner of the 3166 // page, assume this is an attempt to scroll off the title bar, and 3167 // animate the title bar off screen slowly enough that the user can see 3168 // it. 3169 if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0 3170 && mTitleBar != null) { 3171 // FIXME: 100 should be defined somewhere as our max progress. 3172 if (getProgress() < 100) { 3173 // Wait to scroll the title bar off screen until the page has 3174 // finished loading. Keep track of the URL and the destination 3175 // Y position 3176 mPageThatNeedsToSlideTitleBarOffScreen = getUrl(); 3177 mYDistanceToSlideTitleOffScreen = vy; 3178 } else { 3179 pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION); 3180 } 3181 // Since we are animating, we have not yet reached the desired 3182 // scroll position. Do not return true to request another attempt 3183 return false; 3184 } 3185 pinScrollTo(vx, vy, false, 0); 3186 // If the request was to scroll to a negative coordinate, treat it as if 3187 // it was a request to scroll to 0 3188 if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) { 3189 return true; 3190 } else { 3191 return false; 3192 } 3193 } 3194 3195 // scale from content to view coordinates, and pin 3196 private void spawnContentScrollTo(int cx, int cy) { 3197 if (mDrawHistory) { 3198 // disallow WebView to change the scroll position as History Picture 3199 // is used in the view system. 3200 return; 3201 } 3202 int vx = contentToViewX(cx); 3203 int vy = contentToViewY(cy); 3204 pinScrollTo(vx, vy, true, 0); 3205 } 3206 3207 /** 3208 * These are from webkit, and are in content coordinate system (unzoomed) 3209 */ 3210 private void contentSizeChanged(boolean updateLayout) { 3211 // suppress 0,0 since we usually see real dimensions soon after 3212 // this avoids drawing the prev content in a funny place. If we find a 3213 // way to consolidate these notifications, this check may become 3214 // obsolete 3215 if ((mContentWidth | mContentHeight) == 0) { 3216 return; 3217 } 3218 3219 if (mHeightCanMeasure) { 3220 if (getMeasuredHeight() != contentToViewDimension(mContentHeight) 3221 || updateLayout) { 3222 requestLayout(); 3223 } 3224 } else if (mWidthCanMeasure) { 3225 if (getMeasuredWidth() != contentToViewDimension(mContentWidth) 3226 || updateLayout) { 3227 requestLayout(); 3228 } 3229 } else { 3230 // If we don't request a layout, try to send our view size to the 3231 // native side to ensure that WebCore has the correct dimensions. 3232 sendViewSizeZoom(); 3233 } 3234 } 3235 3236 /** 3237 * Set the WebViewClient that will receive various notifications and 3238 * requests. This will replace the current handler. 3239 * @param client An implementation of WebViewClient. 3240 */ 3241 public void setWebViewClient(WebViewClient client) { 3242 mCallbackProxy.setWebViewClient(client); 3243 } 3244 3245 /** 3246 * Gets the WebViewClient 3247 * @return the current WebViewClient instance. 3248 * 3249 *@hide pending API council approval. 3250 */ 3251 public WebViewClient getWebViewClient() { 3252 return mCallbackProxy.getWebViewClient(); 3253 } 3254 3255 /** 3256 * Register the interface to be used when content can not be handled by 3257 * the rendering engine, and should be downloaded instead. This will replace 3258 * the current handler. 3259 * @param listener An implementation of DownloadListener. 3260 */ 3261 public void setDownloadListener(DownloadListener listener) { 3262 mCallbackProxy.setDownloadListener(listener); 3263 } 3264 3265 /** 3266 * Set the chrome handler. This is an implementation of WebChromeClient for 3267 * use in handling Javascript dialogs, favicons, titles, and the progress. 3268 * This will replace the current handler. 3269 * @param client An implementation of WebChromeClient. 3270 */ 3271 public void setWebChromeClient(WebChromeClient client) { 3272 mCallbackProxy.setWebChromeClient(client); 3273 } 3274 3275 /** 3276 * Gets the chrome handler. 3277 * @return the current WebChromeClient instance. 3278 * 3279 * @hide API council approval. 3280 */ 3281 public WebChromeClient getWebChromeClient() { 3282 return mCallbackProxy.getWebChromeClient(); 3283 } 3284 3285 /** 3286 * Set the back/forward list client. This is an implementation of 3287 * WebBackForwardListClient for handling new items and changes in the 3288 * history index. 3289 * @param client An implementation of WebBackForwardListClient. 3290 * {@hide} 3291 */ 3292 public void setWebBackForwardListClient(WebBackForwardListClient client) { 3293 mCallbackProxy.setWebBackForwardListClient(client); 3294 } 3295 3296 /** 3297 * Gets the WebBackForwardListClient. 3298 * {@hide} 3299 */ 3300 public WebBackForwardListClient getWebBackForwardListClient() { 3301 return mCallbackProxy.getWebBackForwardListClient(); 3302 } 3303 3304 /** 3305 * Set the Picture listener. This is an interface used to receive 3306 * notifications of a new Picture. 3307 * @param listener An implementation of WebView.PictureListener. 3308 */ 3309 public void setPictureListener(PictureListener listener) { 3310 mPictureListener = listener; 3311 } 3312 3313 /** 3314 * {@hide} 3315 */ 3316 /* FIXME: Debug only! Remove for SDK! */ 3317 public void externalRepresentation(Message callback) { 3318 mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback); 3319 } 3320 3321 /** 3322 * {@hide} 3323 */ 3324 /* FIXME: Debug only! Remove for SDK! */ 3325 public void documentAsText(Message callback) { 3326 mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback); 3327 } 3328 3329 /** 3330 * Use this function to bind an object to Javascript so that the 3331 * methods can be accessed from Javascript. 3332 * <p><strong>IMPORTANT:</strong> 3333 * <ul> 3334 * <li> Using addJavascriptInterface() allows JavaScript to control your 3335 * application. This can be a very useful feature or a dangerous security 3336 * issue. When the HTML in the WebView is untrustworthy (for example, part 3337 * or all of the HTML is provided by some person or process), then an 3338 * attacker could inject HTML that will execute your code and possibly any 3339 * code of the attacker's choosing.<br> 3340 * Do not use addJavascriptInterface() unless all of the HTML in this 3341 * WebView was written by you.</li> 3342 * <li> The Java object that is bound runs in another thread and not in 3343 * the thread that it was constructed in.</li> 3344 * </ul></p> 3345 * @param obj The class instance to bind to Javascript 3346 * @param interfaceName The name to used to expose the class in Javascript 3347 */ 3348 public void addJavascriptInterface(Object obj, String interfaceName) { 3349 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); 3350 arg.mObject = obj; 3351 arg.mInterfaceName = interfaceName; 3352 mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); 3353 } 3354 3355 /** 3356 * Return the WebSettings object used to control the settings for this 3357 * WebView. 3358 * @return A WebSettings object that can be used to control this WebView's 3359 * settings. 3360 */ 3361 public WebSettings getSettings() { 3362 return mWebViewCore.getSettings(); 3363 } 3364 3365 /** 3366 * Use this method to inform the webview about packages that are installed 3367 * in the system. This information will be used by the 3368 * navigator.isApplicationInstalled() API. 3369 * @param packageNames is a set of package names that are known to be 3370 * installed in the system. 3371 * 3372 * @hide not a public API 3373 */ 3374 public void addPackageNames(Set<String> packageNames) { 3375 mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, packageNames); 3376 } 3377 3378 /** 3379 * Use this method to inform the webview about single packages that are 3380 * installed in the system. This information will be used by the 3381 * navigator.isApplicationInstalled() API. 3382 * @param packageName is the name of a package that is known to be 3383 * installed in the system. 3384 * 3385 * @hide not a public API 3386 */ 3387 public void addPackageName(String packageName) { 3388 mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAME, packageName); 3389 } 3390 3391 /** 3392 * Use this method to inform the webview about packages that are uninstalled 3393 * in the system. This information will be used by the 3394 * navigator.isApplicationInstalled() API. 3395 * @param packageName is the name of a package that has been uninstalled in 3396 * the system. 3397 * 3398 * @hide not a public API 3399 */ 3400 public void removePackageName(String packageName) { 3401 mWebViewCore.sendMessage(EventHub.REMOVE_PACKAGE_NAME, packageName); 3402 } 3403 3404 /** 3405 * Return the list of currently loaded plugins. 3406 * @return The list of currently loaded plugins. 3407 * 3408 * @deprecated This was used for Gears, which has been deprecated. 3409 */ 3410 @Deprecated 3411 public static synchronized PluginList getPluginList() { 3412 return new PluginList(); 3413 } 3414 3415 /** 3416 * @deprecated This was used for Gears, which has been deprecated. 3417 */ 3418 @Deprecated 3419 public void refreshPlugins(boolean reloadOpenPages) { } 3420 3421 //------------------------------------------------------------------------- 3422 // Override View methods 3423 //------------------------------------------------------------------------- 3424 3425 @Override 3426 protected void finalize() throws Throwable { 3427 try { 3428 destroy(); 3429 } finally { 3430 super.finalize(); 3431 } 3432 } 3433 3434 @Override 3435 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 3436 if (child == mTitleBar) { 3437 // When drawing the title bar, move it horizontally to always show 3438 // at the top of the WebView. 3439 mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft()); 3440 } 3441 return super.drawChild(canvas, child, drawingTime); 3442 } 3443 3444 private void drawContent(Canvas canvas) { 3445 // Update the buttons in the picture, so when we draw the picture 3446 // to the screen, they are in the correct state. 3447 // Tell the native side if user is a) touching the screen, 3448 // b) pressing the trackball down, or c) pressing the enter key 3449 // If the cursor is on a button, we need to draw it in the pressed 3450 // state. 3451 // If mNativeClass is 0, we should not reach here, so we do not 3452 // need to check it again. 3453 nativeRecordButtons(hasFocus() && hasWindowFocus(), 3454 mTouchMode == TOUCH_SHORTPRESS_START_MODE 3455 || mTrackballDown || mGotCenterDown, false); 3456 drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); 3457 } 3458 3459 @Override 3460 protected void onDraw(Canvas canvas) { 3461 // if mNativeClass is 0, the WebView has been destroyed. Do nothing. 3462 if (mNativeClass == 0) { 3463 return; 3464 } 3465 3466 // if both mContentWidth and mContentHeight are 0, it means there is no 3467 // valid Picture passed to WebView yet. This can happen when WebView 3468 // just starts. Draw the background and return. 3469 if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) { 3470 canvas.drawColor(mBackgroundColor); 3471 return; 3472 } 3473 3474 int saveCount = canvas.save(); 3475 if (mInOverScrollMode && !getSettings() 3476 .getUseWebViewBackgroundForOverscrollBackground()) { 3477 if (mOverScrollBackground == null) { 3478 mOverScrollBackground = new Paint(); 3479 Bitmap bm = BitmapFactory.decodeResource( 3480 mContext.getResources(), 3481 com.android.internal.R.drawable.status_bar_background); 3482 mOverScrollBackground.setShader(new BitmapShader(bm, 3483 Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); 3484 mOverScrollBorder = new Paint(); 3485 mOverScrollBorder.setStyle(Paint.Style.STROKE); 3486 mOverScrollBorder.setStrokeWidth(0); 3487 mOverScrollBorder.setColor(0xffbbbbbb); 3488 } 3489 3490 int top = 0; 3491 int right = computeRealHorizontalScrollRange(); 3492 int bottom = top + computeRealVerticalScrollRange(); 3493 // first draw the background and anchor to the top of the view 3494 canvas.save(); 3495 canvas.translate(mScrollX, mScrollY); 3496 canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom 3497 - mScrollY, Region.Op.DIFFERENCE); 3498 canvas.drawPaint(mOverScrollBackground); 3499 canvas.restore(); 3500 // then draw the border 3501 canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder); 3502 // next clip the region for the content 3503 canvas.clipRect(0, top, right, bottom); 3504 } 3505 if (mTitleBar != null) { 3506 canvas.translate(0, (int) mTitleBar.getHeight()); 3507 } 3508 if (mDragTrackerHandler == null) { 3509 drawContent(canvas); 3510 } else { 3511 if (!mDragTrackerHandler.draw(canvas)) { 3512 // sometimes the tracker doesn't draw, even though its active 3513 drawContent(canvas); 3514 } 3515 if (mDragTrackerHandler.isFinished()) { 3516 mDragTrackerHandler = null; 3517 } 3518 } 3519 canvas.restoreToCount(saveCount); 3520 3521 // Now draw the shadow. 3522 int titleH = getVisibleTitleHeight(); 3523 if (mTitleBar != null && titleH == 0) { 3524 int height = (int) (5f * getContext().getResources() 3525 .getDisplayMetrics().density); 3526 mTitleShadow.setBounds(mScrollX, mScrollY, mScrollX + getWidth(), 3527 mScrollY + height); 3528 mTitleShadow.draw(canvas); 3529 } 3530 3531 if (AUTO_REDRAW_HACK && mAutoRedraw) { 3532 invalidate(); 3533 } 3534 if (inEditingMode()) { 3535 mWebTextView.onDrawSubstitute(); 3536 } 3537 mWebViewCore.signalRepaintDone(); 3538 } 3539 3540 @Override 3541 public void draw(Canvas canvas) { 3542 super.draw(canvas); 3543 if (mEdgeGlowTop != null && drawEdgeGlows(canvas)) { 3544 invalidate(); 3545 } 3546 } 3547 3548 /** 3549 * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null. 3550 * 3551 * @param canvas Canvas to draw into, transformed into view coordinates. 3552 * @return true if glow effects are still animating and the view should invalidate again. 3553 */ 3554 private boolean drawEdgeGlows(Canvas canvas) { 3555 final int scrollX = mScrollX; 3556 final int scrollY = mScrollY; 3557 final int width = getWidth(); 3558 int height = getHeight(); 3559 3560 boolean invalidateForGlow = false; 3561 if (!mEdgeGlowTop.isFinished()) { 3562 final int restoreCount = canvas.save(); 3563 3564 canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY)); 3565 mEdgeGlowTop.setSize(width * 2, height); 3566 invalidateForGlow |= mEdgeGlowTop.draw(canvas); 3567 canvas.restoreToCount(restoreCount); 3568 } 3569 if (!mEdgeGlowBottom.isFinished()) { 3570 final int restoreCount = canvas.save(); 3571 3572 canvas.translate(-width / 2 + scrollX, Math.max(computeMaxScrollY(), scrollY) + height); 3573 canvas.rotate(180, width, 0); 3574 mEdgeGlowBottom.setSize(width * 2, height); 3575 invalidateForGlow |= mEdgeGlowBottom.draw(canvas); 3576 canvas.restoreToCount(restoreCount); 3577 } 3578 if (!mEdgeGlowLeft.isFinished()) { 3579 final int restoreCount = canvas.save(); 3580 3581 canvas.rotate(270); 3582 canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX)); 3583 mEdgeGlowLeft.setSize(height * 2, width); 3584 invalidateForGlow |= mEdgeGlowLeft.draw(canvas); 3585 canvas.restoreToCount(restoreCount); 3586 } 3587 if (!mEdgeGlowRight.isFinished()) { 3588 final int restoreCount = canvas.save(); 3589 3590 canvas.rotate(90); 3591 canvas.translate(-height / 2 + scrollY, 3592 -(Math.max(computeMaxScrollX(), scrollX) + width)); 3593 mEdgeGlowRight.setSize(height * 2, width); 3594 invalidateForGlow |= mEdgeGlowRight.draw(canvas); 3595 canvas.restoreToCount(restoreCount); 3596 } 3597 return invalidateForGlow; 3598 } 3599 3600 @Override 3601 public void setLayoutParams(ViewGroup.LayoutParams params) { 3602 if (params.height == LayoutParams.WRAP_CONTENT) { 3603 mWrapContent = true; 3604 } 3605 super.setLayoutParams(params); 3606 } 3607 3608 @Override 3609 public boolean performLongClick() { 3610 // performLongClick() is the result of a delayed message. If we switch 3611 // to windows overview, the WebView will be temporarily removed from the 3612 // view system. In that case, do nothing. 3613 if (getParent() == null) return false; 3614 if (mNativeClass != 0 && nativeCursorIsTextInput()) { 3615 // Send the click so that the textfield is in focus 3616 centerKeyPressOnTextField(); 3617 rebuildWebTextView(); 3618 } else { 3619 clearTextEntry(true); 3620 } 3621 if (inEditingMode()) { 3622 return mWebTextView.performLongClick(); 3623 } 3624 /* if long click brings up a context menu, the super function 3625 * returns true and we're done. Otherwise, nothing happened when 3626 * the user clicked. */ 3627 if (super.performLongClick()) { 3628 return true; 3629 } 3630 /* In the case where the application hasn't already handled the long 3631 * click action, look for a word under the click. If one is found, 3632 * animate the text selection into view. 3633 * FIXME: no animation code yet */ 3634 if (mSelectingText) return false; // long click does nothing on selection 3635 int x = viewToContentX((int) mLastTouchX + mScrollX); 3636 int y = viewToContentY((int) mLastTouchY + mScrollY); 3637 setUpSelect(); 3638 if (mNativeClass != 0 && nativeWordSelection(x, y)) { 3639 nativeSetExtendSelection(); 3640 WebChromeClient client = getWebChromeClient(); 3641 if (client != null) client.onSelectionStart(this); 3642 return true; 3643 } 3644 notifySelectDialogDismissed(); 3645 return false; 3646 } 3647 3648 boolean inAnimateZoom() { 3649 return mZoomScale != 0; 3650 } 3651 3652 /** 3653 * Need to adjust the WebTextView after a change in zoom, since mActualScale 3654 * has changed. This is especially important for password fields, which are 3655 * drawn by the WebTextView, since it conveys more information than what 3656 * webkit draws. Thus we need to reposition it to show in the correct 3657 * place. 3658 */ 3659 private boolean mNeedToAdjustWebTextView; 3660 3661 private boolean didUpdateTextViewBounds(boolean allowIntersect) { 3662 Rect contentBounds = nativeFocusCandidateNodeBounds(); 3663 Rect vBox = contentToViewRect(contentBounds); 3664 Rect visibleRect = new Rect(); 3665 calcOurVisibleRect(visibleRect); 3666 // If the textfield is on screen, place the WebTextView in 3667 // its new place, accounting for our new scroll/zoom values, 3668 // and adjust its textsize. 3669 if (allowIntersect ? Rect.intersects(visibleRect, vBox) 3670 : visibleRect.contains(vBox)) { 3671 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), 3672 vBox.height()); 3673 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 3674 contentToViewDimension( 3675 nativeFocusCandidateTextSize())); 3676 return true; 3677 } else { 3678 // The textfield is now off screen. The user probably 3679 // was not zooming to see the textfield better. Remove 3680 // the WebTextView. If the user types a key, and the 3681 // textfield is still in focus, we will reconstruct 3682 // the WebTextView and scroll it back on screen. 3683 mWebTextView.remove(); 3684 return false; 3685 } 3686 } 3687 3688 private void drawExtras(Canvas canvas, int extras, boolean animationsRunning) { 3689 // If mNativeClass is 0, we should not reach here, so we do not 3690 // need to check it again. 3691 if (animationsRunning) { 3692 canvas.setDrawFilter(mWebViewCore.mZoomFilter); 3693 } 3694 nativeDrawExtras(canvas, extras); 3695 canvas.setDrawFilter(null); 3696 } 3697 3698 private void drawCoreAndCursorRing(Canvas canvas, int color, 3699 boolean drawCursorRing) { 3700 if (mDrawHistory) { 3701 canvas.scale(mActualScale, mActualScale); 3702 canvas.drawPicture(mHistoryPicture); 3703 return; 3704 } 3705 3706 boolean animateZoom = mZoomScale != 0; 3707 boolean animateScroll = ((!mScroller.isFinished() 3708 || mVelocityTracker != null) 3709 && (mTouchMode != TOUCH_DRAG_MODE || 3710 mHeldMotionless != MOTIONLESS_TRUE)) 3711 || mDeferTouchMode == TOUCH_DRAG_MODE; 3712 if (mTouchMode == TOUCH_DRAG_MODE) { 3713 if (mHeldMotionless == MOTIONLESS_PENDING) { 3714 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 3715 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 3716 mHeldMotionless = MOTIONLESS_FALSE; 3717 } 3718 if (mHeldMotionless == MOTIONLESS_FALSE) { 3719 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3720 .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME); 3721 mHeldMotionless = MOTIONLESS_PENDING; 3722 } 3723 } 3724 if (animateZoom) { 3725 float zoomScale; 3726 int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); 3727 if (interval < ZOOM_ANIMATION_LENGTH) { 3728 float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; 3729 zoomScale = 1.0f / (mInvInitialZoomScale 3730 + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); 3731 invalidate(); 3732 } else { 3733 zoomScale = mZoomScale; 3734 // set mZoomScale to be 0 as we have done animation 3735 mZoomScale = 0; 3736 WebViewCore.resumeUpdatePicture(mWebViewCore); 3737 // call invalidate() again to draw with the final filters 3738 invalidate(); 3739 if (mNeedToAdjustWebTextView) { 3740 mNeedToAdjustWebTextView = false; 3741 if (didUpdateTextViewBounds(false) 3742 && nativeFocusCandidateIsPassword()) { 3743 // If it is a password field, start drawing the 3744 // WebTextView once again. 3745 mWebTextView.setInPassword(true); 3746 } 3747 } 3748 } 3749 // calculate the intermediate scroll position. As we need to use 3750 // zoomScale, we can't use pinLocX/Y directly. Copy the logic here. 3751 float scale = zoomScale * mInvInitialZoomScale; 3752 int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) 3753 - mZoomCenterX); 3754 tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth 3755 * zoomScale)) + mScrollX; 3756 int titleHeight = getTitleHeight(); 3757 int ty = Math.round(scale 3758 * (mInitialScrollY + mZoomCenterY - titleHeight) 3759 - (mZoomCenterY - titleHeight)); 3760 ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty 3761 - titleHeight, getViewHeight(), Math.round(mContentHeight 3762 * zoomScale)) + titleHeight) + mScrollY; 3763 canvas.translate(tx, ty); 3764 canvas.scale(zoomScale, zoomScale); 3765 if (inEditingMode() && !mNeedToAdjustWebTextView 3766 && mZoomScale != 0) { 3767 // The WebTextView is up. Keep track of this so we can adjust 3768 // its size and placement when we finish zooming 3769 mNeedToAdjustWebTextView = true; 3770 // If it is in password mode, turn it off so it does not draw 3771 // misplaced. 3772 if (nativeFocusCandidateIsPassword()) { 3773 mWebTextView.setInPassword(false); 3774 } 3775 } 3776 } else { 3777 canvas.scale(mActualScale, mActualScale); 3778 } 3779 3780 boolean UIAnimationsRunning = false; 3781 // Currently for each draw we compute the animation values; 3782 // We may in the future decide to do that independently. 3783 if (mNativeClass != 0 && nativeEvaluateLayersAnimations()) { 3784 UIAnimationsRunning = true; 3785 // If we have unfinished (or unstarted) animations, 3786 // we ask for a repaint. 3787 invalidate(); 3788 } 3789 mWebViewCore.drawContentPicture(canvas, color, 3790 (animateZoom || mPreviewZoomOnly || UIAnimationsRunning), 3791 animateScroll); 3792 if (mNativeClass == 0) return; 3793 // decide which adornments to draw 3794 int extras = DRAW_EXTRAS_NONE; 3795 if (mFindIsUp) { 3796 extras = DRAW_EXTRAS_FIND; 3797 } else if (mSelectingText) { 3798 extras = DRAW_EXTRAS_SELECTION; 3799 nativeSetSelectionPointer(mDrawSelectionPointer, 3800 mInvActualScale, 3801 mSelectX, mSelectY - getTitleHeight()); 3802 } else if (drawCursorRing) { 3803 extras = DRAW_EXTRAS_CURSOR_RING; 3804 } 3805 drawExtras(canvas, extras, UIAnimationsRunning); 3806 3807 if (extras == DRAW_EXTRAS_CURSOR_RING) { 3808 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 3809 mTouchMode = TOUCH_SHORTPRESS_MODE; 3810 } 3811 } 3812 if (mFocusSizeChanged) { 3813 mFocusSizeChanged = false; 3814 // If we are zooming, this will get handled above, when the zoom 3815 // finishes. We also do not need to do this unless the WebTextView 3816 // is showing. 3817 if (!animateZoom && inEditingMode()) { 3818 didUpdateTextViewBounds(true); 3819 } 3820 } 3821 } 3822 3823 // draw history 3824 private boolean mDrawHistory = false; 3825 private Picture mHistoryPicture = null; 3826 private int mHistoryWidth = 0; 3827 private int mHistoryHeight = 0; 3828 3829 // Only check the flag, can be called from WebCore thread 3830 boolean drawHistory() { 3831 return mDrawHistory; 3832 } 3833 3834 // Should only be called in UI thread 3835 void switchOutDrawHistory() { 3836 if (null == mWebViewCore) return; // CallbackProxy may trigger this 3837 if (mDrawHistory && mWebViewCore.pictureReady()) { 3838 mDrawHistory = false; 3839 mHistoryPicture = null; 3840 invalidate(); 3841 int oldScrollX = mScrollX; 3842 int oldScrollY = mScrollY; 3843 mScrollX = pinLocX(mScrollX); 3844 mScrollY = pinLocY(mScrollY); 3845 if (oldScrollX != mScrollX || oldScrollY != mScrollY) { 3846 mUserScroll = false; 3847 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX, 3848 oldScrollY); 3849 onScrollChanged(mScrollX, mScrollY, oldScrollX, oldScrollY); 3850 } else { 3851 sendOurVisibleRect(); 3852 } 3853 } 3854 } 3855 3856 WebViewCore.CursorData cursorData() { 3857 WebViewCore.CursorData result = new WebViewCore.CursorData(); 3858 result.mMoveGeneration = nativeMoveGeneration(); 3859 result.mFrame = nativeCursorFramePointer(); 3860 Point position = nativeCursorPosition(); 3861 result.mX = position.x; 3862 result.mY = position.y; 3863 return result; 3864 } 3865 3866 /** 3867 * Delete text from start to end in the focused textfield. If there is no 3868 * focus, or if start == end, silently fail. If start and end are out of 3869 * order, swap them. 3870 * @param start Beginning of selection to delete. 3871 * @param end End of selection to delete. 3872 */ 3873 /* package */ void deleteSelection(int start, int end) { 3874 mTextGeneration++; 3875 WebViewCore.TextSelectionData data 3876 = new WebViewCore.TextSelectionData(start, end); 3877 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0, 3878 data); 3879 } 3880 3881 /** 3882 * Set the selection to (start, end) in the focused textfield. If start and 3883 * end are out of order, swap them. 3884 * @param start Beginning of selection. 3885 * @param end End of selection. 3886 */ 3887 /* package */ void setSelection(int start, int end) { 3888 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end); 3889 } 3890 3891 @Override 3892 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 3893 InputConnection connection = super.onCreateInputConnection(outAttrs); 3894 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN; 3895 return connection; 3896 } 3897 3898 /** 3899 * Called in response to a message from webkit telling us that the soft 3900 * keyboard should be launched. 3901 */ 3902 private void displaySoftKeyboard(boolean isTextView) { 3903 InputMethodManager imm = (InputMethodManager) 3904 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 3905 3906 // bring it back to the default scale so that user can enter text 3907 boolean zoom = mActualScale < mDefaultScale; 3908 if (zoom) { 3909 mInZoomOverview = false; 3910 mZoomCenterX = mLastTouchX; 3911 mZoomCenterY = mLastTouchY; 3912 // do not change text wrap scale so that there is no reflow 3913 setNewZoomScale(mDefaultScale, false, false); 3914 } 3915 if (isTextView) { 3916 rebuildWebTextView(); 3917 if (inEditingMode()) { 3918 imm.showSoftInput(mWebTextView, 0); 3919 if (zoom) { 3920 didUpdateTextViewBounds(true); 3921 } 3922 return; 3923 } 3924 } 3925 // Used by plugins. 3926 // Also used if the navigation cache is out of date, and 3927 // does not recognize that a textfield is in focus. In that 3928 // case, use WebView as the targeted view. 3929 // see http://b/issue?id=2457459 3930 imm.showSoftInput(this, 0); 3931 } 3932 3933 // Called by WebKit to instruct the UI to hide the keyboard 3934 private void hideSoftKeyboard() { 3935 InputMethodManager imm = (InputMethodManager) 3936 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 3937 3938 imm.hideSoftInputFromWindow(this.getWindowToken(), 0); 3939 } 3940 3941 /* 3942 * This method checks the current focus and cursor and potentially rebuilds 3943 * mWebTextView to have the appropriate properties, such as password, 3944 * multiline, and what text it contains. It also removes it if necessary. 3945 */ 3946 /* package */ void rebuildWebTextView() { 3947 // If the WebView does not have focus, do nothing until it gains focus. 3948 if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) { 3949 return; 3950 } 3951 boolean alreadyThere = inEditingMode(); 3952 // inEditingMode can only return true if mWebTextView is non-null, 3953 // so we can safely call remove() if (alreadyThere) 3954 if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) { 3955 if (alreadyThere) { 3956 mWebTextView.remove(); 3957 } 3958 return; 3959 } 3960 // At this point, we know we have found an input field, so go ahead 3961 // and create the WebTextView if necessary. 3962 if (mWebTextView == null) { 3963 mWebTextView = new WebTextView(mContext, WebView.this); 3964 // Initialize our generation number. 3965 mTextGeneration = 0; 3966 } 3967 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 3968 contentToViewDimension(nativeFocusCandidateTextSize())); 3969 Rect visibleRect = new Rect(); 3970 calcOurContentVisibleRect(visibleRect); 3971 // Note that sendOurVisibleRect calls viewToContent, so the coordinates 3972 // should be in content coordinates. 3973 Rect bounds = nativeFocusCandidateNodeBounds(); 3974 Rect vBox = contentToViewRect(bounds); 3975 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height()); 3976 if (!Rect.intersects(bounds, visibleRect)) { 3977 mWebTextView.bringIntoView(); 3978 } 3979 String text = nativeFocusCandidateText(); 3980 int nodePointer = nativeFocusCandidatePointer(); 3981 if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) { 3982 // It is possible that we have the same textfield, but it has moved, 3983 // i.e. In the case of opening/closing the screen. 3984 // In that case, we need to set the dimensions, but not the other 3985 // aspects. 3986 // If the text has been changed by webkit, update it. However, if 3987 // there has been more UI text input, ignore it. We will receive 3988 // another update when that text is recognized. 3989 if (text != null && !text.equals(mWebTextView.getText().toString()) 3990 && nativeTextGeneration() == mTextGeneration) { 3991 mWebTextView.setTextAndKeepSelection(text); 3992 } 3993 } else { 3994 mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ? 3995 Gravity.RIGHT : Gravity.NO_GRAVITY); 3996 // This needs to be called before setType, which may call 3997 // requestFormData, and it needs to have the correct nodePointer. 3998 mWebTextView.setNodePointer(nodePointer); 3999 mWebTextView.setType(nativeFocusCandidateType()); 4000 Rect paddingRect = nativeFocusCandidatePaddingRect(); 4001 if (paddingRect != null) { 4002 // Use contentToViewDimension since these are the dimensions of 4003 // the padding. 4004 mWebTextView.setPadding( 4005 contentToViewDimension(paddingRect.left), 4006 contentToViewDimension(paddingRect.top), 4007 contentToViewDimension(paddingRect.right), 4008 contentToViewDimension(paddingRect.bottom)); 4009 } 4010 if (null == text) { 4011 if (DebugFlags.WEB_VIEW) { 4012 Log.v(LOGTAG, "rebuildWebTextView null == text"); 4013 } 4014 text = ""; 4015 } 4016 mWebTextView.setTextAndKeepSelection(text); 4017 InputMethodManager imm = InputMethodManager.peekInstance(); 4018 if (imm != null && imm.isActive(mWebTextView)) { 4019 imm.restartInput(mWebTextView); 4020 } 4021 } 4022 mWebTextView.requestFocus(); 4023 } 4024 4025 /** 4026 * Called by WebTextView to find saved form data associated with the 4027 * textfield 4028 * @param name Name of the textfield. 4029 * @param nodePointer Pointer to the node of the textfield, so it can be 4030 * compared to the currently focused textfield when the data is 4031 * retrieved. 4032 */ 4033 /* package */ void requestFormData(String name, int nodePointer) { 4034 if (mWebViewCore.getSettings().getSaveFormData()) { 4035 Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA); 4036 update.arg1 = nodePointer; 4037 RequestFormData updater = new RequestFormData(name, getUrl(), 4038 update); 4039 Thread t = new Thread(updater); 4040 t.start(); 4041 } 4042 } 4043 4044 /** 4045 * Pass a message to find out the <label> associated with the <input> 4046 * identified by nodePointer 4047 * @param framePointer Pointer to the frame containing the <input> node 4048 * @param nodePointer Pointer to the node for which a <label> is desired. 4049 */ 4050 /* package */ void requestLabel(int framePointer, int nodePointer) { 4051 mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer, 4052 nodePointer); 4053 } 4054 4055 /* 4056 * This class requests an Adapter for the WebTextView which shows past 4057 * entries stored in the database. It is a Runnable so that it can be done 4058 * in its own thread, without slowing down the UI. 4059 */ 4060 private class RequestFormData implements Runnable { 4061 private String mName; 4062 private String mUrl; 4063 private Message mUpdateMessage; 4064 4065 public RequestFormData(String name, String url, Message msg) { 4066 mName = name; 4067 mUrl = url; 4068 mUpdateMessage = msg; 4069 } 4070 4071 public void run() { 4072 ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName); 4073 if (pastEntries.size() > 0) { 4074 AutoCompleteAdapter adapter = new 4075 AutoCompleteAdapter(mContext, pastEntries); 4076 mUpdateMessage.obj = adapter; 4077 mUpdateMessage.sendToTarget(); 4078 } 4079 } 4080 } 4081 4082 /** 4083 * Dump the display tree to "/sdcard/displayTree.txt" 4084 * 4085 * @hide debug only 4086 */ 4087 public void dumpDisplayTree() { 4088 nativeDumpDisplayTree(getUrl()); 4089 } 4090 4091 /** 4092 * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to 4093 * "/sdcard/domTree.txt" 4094 * 4095 * @hide debug only 4096 */ 4097 public void dumpDomTree(boolean toFile) { 4098 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0); 4099 } 4100 4101 /** 4102 * Dump the render tree to adb shell if "toFile" is False, otherwise dump it 4103 * to "/sdcard/renderTree.txt" 4104 * 4105 * @hide debug only 4106 */ 4107 public void dumpRenderTree(boolean toFile) { 4108 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0); 4109 } 4110 4111 /** 4112 * Dump the V8 counters to standard output. 4113 * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to 4114 * true. Otherwise, this will do nothing. 4115 * 4116 * @hide debug only 4117 */ 4118 public void dumpV8Counters() { 4119 mWebViewCore.sendMessage(EventHub.DUMP_V8COUNTERS); 4120 } 4121 4122 // This is used to determine long press with the center key. Does not 4123 // affect long press with the trackball/touch. 4124 private boolean mGotCenterDown = false; 4125 4126 @Override 4127 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 4128 // send complex characters to webkit for use by JS and plugins 4129 if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) { 4130 // pass the key to DOM 4131 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 4132 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 4133 // return true as DOM handles the key 4134 return true; 4135 } 4136 return false; 4137 } 4138 4139 @Override 4140 public boolean onKeyDown(int keyCode, KeyEvent event) { 4141 if (DebugFlags.WEB_VIEW) { 4142 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() 4143 + ", " + event + ", unicode=" + event.getUnicodeChar()); 4144 } 4145 4146 if (mNativeClass == 0) { 4147 return false; 4148 } 4149 4150 // do this hack up front, so it always works, regardless of touch-mode 4151 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) { 4152 mAutoRedraw = !mAutoRedraw; 4153 if (mAutoRedraw) { 4154 invalidate(); 4155 } 4156 return true; 4157 } 4158 4159 // Bubble up the key event if 4160 // 1. it is a system key; or 4161 // 2. the host application wants to handle it; 4162 if (event.isSystem() 4163 || mCallbackProxy.uiOverrideKeyEvent(event)) { 4164 return false; 4165 } 4166 4167 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 4168 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 4169 if (nativeFocusIsPlugin()) { 4170 mShiftIsPressed = true; 4171 } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) { 4172 setUpSelect(); 4173 } 4174 } 4175 4176 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 4177 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 4178 switchOutDrawHistory(); 4179 if (nativeFocusIsPlugin()) { 4180 letPluginHandleNavKey(keyCode, event.getEventTime(), true); 4181 return true; 4182 } 4183 if (mSelectingText) { 4184 int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT 4185 ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0; 4186 int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ? 4187 -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0; 4188 int multiplier = event.getRepeatCount() + 1; 4189 moveSelection(xRate * multiplier, yRate * multiplier); 4190 return true; 4191 } 4192 if (navHandledKey(keyCode, 1, false, event.getEventTime())) { 4193 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 4194 return true; 4195 } 4196 // Bubble up the key event as WebView doesn't handle it 4197 return false; 4198 } 4199 4200 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 4201 switchOutDrawHistory(); 4202 if (event.getRepeatCount() == 0) { 4203 if (mSelectingText) { 4204 return true; // discard press if copy in progress 4205 } 4206 mGotCenterDown = true; 4207 mPrivateHandler.sendMessageDelayed(mPrivateHandler 4208 .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); 4209 // Already checked mNativeClass, so we do not need to check it 4210 // again. 4211 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 4212 return true; 4213 } 4214 // Bubble up the key event as WebView doesn't handle it 4215 return false; 4216 } 4217 4218 if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT 4219 && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) { 4220 // turn off copy select if a shift-key combo is pressed 4221 selectionDone(); 4222 mShiftIsPressed = false; 4223 } 4224 4225 if (getSettings().getNavDump()) { 4226 switch (keyCode) { 4227 case KeyEvent.KEYCODE_4: 4228 dumpDisplayTree(); 4229 break; 4230 case KeyEvent.KEYCODE_5: 4231 case KeyEvent.KEYCODE_6: 4232 dumpDomTree(keyCode == KeyEvent.KEYCODE_5); 4233 break; 4234 case KeyEvent.KEYCODE_7: 4235 case KeyEvent.KEYCODE_8: 4236 dumpRenderTree(keyCode == KeyEvent.KEYCODE_7); 4237 break; 4238 case KeyEvent.KEYCODE_9: 4239 nativeInstrumentReport(); 4240 return true; 4241 } 4242 } 4243 4244 if (nativeCursorIsTextInput()) { 4245 // This message will put the node in focus, for the DOM's notion 4246 // of focus, and make the focuscontroller active 4247 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 4248 nativeCursorNodePointer()); 4249 // This will bring up the WebTextView and put it in focus, for 4250 // our view system's notion of focus 4251 rebuildWebTextView(); 4252 // Now we need to pass the event to it 4253 if (inEditingMode()) { 4254 mWebTextView.setDefaultSelection(); 4255 return mWebTextView.dispatchKeyEvent(event); 4256 } 4257 } else if (nativeHasFocusNode()) { 4258 // In this case, the cursor is not on a text input, but the focus 4259 // might be. Check it, and if so, hand over to the WebTextView. 4260 rebuildWebTextView(); 4261 if (inEditingMode()) { 4262 mWebTextView.setDefaultSelection(); 4263 return mWebTextView.dispatchKeyEvent(event); 4264 } 4265 } 4266 4267 // TODO: should we pass all the keys to DOM or check the meta tag 4268 if (nativeCursorWantsKeyEvents() || true) { 4269 // pass the key to DOM 4270 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 4271 // return true as DOM handles the key 4272 return true; 4273 } 4274 4275 // Bubble up the key event as WebView doesn't handle it 4276 return false; 4277 } 4278 4279 @Override 4280 public boolean onKeyUp(int keyCode, KeyEvent event) { 4281 if (DebugFlags.WEB_VIEW) { 4282 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() 4283 + ", " + event + ", unicode=" + event.getUnicodeChar()); 4284 } 4285 4286 if (mNativeClass == 0) { 4287 return false; 4288 } 4289 4290 // special CALL handling when cursor node's href is "tel:XXX" 4291 if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) { 4292 String text = nativeCursorText(); 4293 if (!nativeCursorIsTextInput() && text != null 4294 && text.startsWith(SCHEME_TEL)) { 4295 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); 4296 getContext().startActivity(intent); 4297 return true; 4298 } 4299 } 4300 4301 // Bubble up the key event if 4302 // 1. it is a system key; or 4303 // 2. the host application wants to handle it; 4304 if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) { 4305 return false; 4306 } 4307 4308 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 4309 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 4310 if (nativeFocusIsPlugin()) { 4311 mShiftIsPressed = false; 4312 } else if (copySelection()) { 4313 selectionDone(); 4314 return true; 4315 } 4316 } 4317 4318 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 4319 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 4320 if (nativeFocusIsPlugin()) { 4321 letPluginHandleNavKey(keyCode, event.getEventTime(), false); 4322 return true; 4323 } 4324 // always handle the navigation keys in the UI thread 4325 // Bubble up the key event as WebView doesn't handle it 4326 return false; 4327 } 4328 4329 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 4330 // remove the long press message first 4331 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 4332 mGotCenterDown = false; 4333 4334 if (mSelectingText) { 4335 if (mExtendSelection) { 4336 copySelection(); 4337 selectionDone(); 4338 } else { 4339 mExtendSelection = true; 4340 nativeSetExtendSelection(); 4341 invalidate(); // draw the i-beam instead of the arrow 4342 } 4343 return true; // discard press if copy in progress 4344 } 4345 4346 // perform the single click 4347 Rect visibleRect = sendOurVisibleRect(); 4348 // Note that sendOurVisibleRect calls viewToContent, so the 4349 // coordinates should be in content coordinates. 4350 if (!nativeCursorIntersects(visibleRect)) { 4351 return false; 4352 } 4353 WebViewCore.CursorData data = cursorData(); 4354 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); 4355 playSoundEffect(SoundEffectConstants.CLICK); 4356 if (nativeCursorIsTextInput()) { 4357 rebuildWebTextView(); 4358 centerKeyPressOnTextField(); 4359 if (inEditingMode()) { 4360 mWebTextView.setDefaultSelection(); 4361 } 4362 return true; 4363 } 4364 clearTextEntry(true); 4365 nativeSetFollowedLink(true); 4366 if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) { 4367 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, 4368 nativeCursorNodePointer()); 4369 } 4370 return true; 4371 } 4372 4373 // TODO: should we pass all the keys to DOM or check the meta tag 4374 if (nativeCursorWantsKeyEvents() || true) { 4375 // pass the key to DOM 4376 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 4377 // return true as DOM handles the key 4378 return true; 4379 } 4380 4381 // Bubble up the key event as WebView doesn't handle it 4382 return false; 4383 } 4384 4385 /** 4386 * @hide pending API council approval. 4387 */ 4388 public void setUpSelect() { 4389 if (0 == mNativeClass) return; // client isn't initialized 4390 if (inFullScreenMode()) return; 4391 if (mSelectingText) return; 4392 mExtendSelection = false; 4393 mSelectingText = mDrawSelectionPointer = true; 4394 // don't let the picture change during text selection 4395 WebViewCore.pauseUpdatePicture(mWebViewCore); 4396 nativeResetSelection(); 4397 if (nativeHasCursorNode()) { 4398 Rect rect = nativeCursorNodeBounds(); 4399 mSelectX = contentToViewX(rect.left); 4400 mSelectY = contentToViewY(rect.top); 4401 } else if (mLastTouchY > getVisibleTitleHeight()) { 4402 mSelectX = mScrollX + (int) mLastTouchX; 4403 mSelectY = mScrollY + (int) mLastTouchY; 4404 } else { 4405 mSelectX = mScrollX + getViewWidth() / 2; 4406 mSelectY = mScrollY + getViewHeightWithTitle() / 2; 4407 } 4408 nativeHideCursor(); 4409 } 4410 4411 /** 4412 * Use this method to put the WebView into text selection mode. 4413 * Do not rely on this functionality; it will be deprecated in the future. 4414 */ 4415 public void emulateShiftHeld() { 4416 setUpSelect(); 4417 } 4418 4419 /** 4420 * @hide pending API council approval. 4421 */ 4422 public void selectAll() { 4423 if (0 == mNativeClass) return; // client isn't initialized 4424 if (inFullScreenMode()) return; 4425 if (!mSelectingText) setUpSelect(); 4426 nativeSelectAll(); 4427 mDrawSelectionPointer = false; 4428 mExtendSelection = true; 4429 invalidate(); 4430 } 4431 4432 /** 4433 * @hide pending API council approval. 4434 */ 4435 public boolean selectDialogIsUp() { 4436 return mSelectingText; 4437 } 4438 4439 /** 4440 * @hide pending API council approval. 4441 */ 4442 public void notifySelectDialogDismissed() { 4443 mSelectingText = false; 4444 WebViewCore.resumeUpdatePicture(mWebViewCore); 4445 } 4446 4447 /** 4448 * @hide pending API council approval. 4449 */ 4450 public void selectionDone() { 4451 if (mSelectingText) { 4452 WebChromeClient client = getWebChromeClient(); 4453 if (client != null) client.onSelectionDone(this); 4454 invalidate(); // redraw without selection 4455 notifySelectDialogDismissed(); 4456 } 4457 } 4458 4459 /** 4460 * @hide pending API council approval. 4461 */ 4462 public boolean copySelection() { 4463 boolean copiedSomething = false; 4464 String selection = getSelection(); 4465 if (selection != "") { 4466 if (DebugFlags.WEB_VIEW) { 4467 Log.v(LOGTAG, "copySelection \"" + selection + "\""); 4468 } 4469 Toast.makeText(mContext 4470 , com.android.internal.R.string.text_copied 4471 , Toast.LENGTH_SHORT).show(); 4472 copiedSomething = true; 4473 try { 4474 IClipboard clip = IClipboard.Stub.asInterface( 4475 ServiceManager.getService("clipboard")); 4476 clip.setClipboardText(selection); 4477 } catch (android.os.RemoteException e) { 4478 Log.e(LOGTAG, "Clipboard failed", e); 4479 } 4480 } 4481 invalidate(); // remove selection region and pointer 4482 return copiedSomething; 4483 } 4484 4485 /** 4486 * @hide pending API council approval. 4487 */ 4488 public String getSelection() { 4489 if (mNativeClass == 0) return ""; 4490 return nativeGetSelection(); 4491 } 4492 4493 @Override 4494 protected void onAttachedToWindow() { 4495 super.onAttachedToWindow(); 4496 if (hasWindowFocus()) setActive(true); 4497 } 4498 4499 @Override 4500 protected void onDetachedFromWindow() { 4501 clearHelpers(); 4502 dismissZoomControl(); 4503 if (hasWindowFocus()) setActive(false); 4504 super.onDetachedFromWindow(); 4505 } 4506 4507 /** 4508 * @deprecated WebView no longer needs to implement 4509 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 4510 */ 4511 @Deprecated 4512 public void onChildViewAdded(View parent, View child) {} 4513 4514 /** 4515 * @deprecated WebView no longer needs to implement 4516 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 4517 */ 4518 @Deprecated 4519 public void onChildViewRemoved(View p, View child) {} 4520 4521 /** 4522 * @deprecated WebView should not have implemented 4523 * ViewTreeObserver.OnGlobalFocusChangeListener. This method 4524 * does nothing now. 4525 */ 4526 @Deprecated 4527 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 4528 } 4529 4530 private void setActive(boolean active) { 4531 if (active) { 4532 if (hasFocus()) { 4533 // If our window regained focus, and we have focus, then begin 4534 // drawing the cursor ring 4535 mDrawCursorRing = true; 4536 if (mNativeClass != 0) { 4537 nativeRecordButtons(true, false, true); 4538 if (inEditingMode()) { 4539 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0); 4540 } 4541 } 4542 } else { 4543 // If our window gained focus, but we do not have it, do not 4544 // draw the cursor ring. 4545 mDrawCursorRing = false; 4546 // We do not call nativeRecordButtons here because we assume 4547 // that when we lost focus, or window focus, it got called with 4548 // false for the first parameter 4549 } 4550 } else { 4551 if (mWebViewCore != null && getSettings().getBuiltInZoomControls() 4552 && (mZoomButtonsController == null || 4553 !mZoomButtonsController.isVisible())) { 4554 /* 4555 * The zoom controls come in their own window, so our window 4556 * loses focus. Our policy is to not draw the cursor ring if 4557 * our window is not focused, but this is an exception since 4558 * the user can still navigate the web page with the zoom 4559 * controls showing. 4560 */ 4561 // If our window has lost focus, stop drawing the cursor ring 4562 mDrawCursorRing = false; 4563 } 4564 mGotKeyDown = false; 4565 mShiftIsPressed = false; 4566 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 4567 mTouchMode = TOUCH_DONE_MODE; 4568 if (mNativeClass != 0) { 4569 nativeRecordButtons(false, false, true); 4570 } 4571 setFocusControllerInactive(); 4572 } 4573 invalidate(); 4574 } 4575 4576 // To avoid drawing the cursor ring, and remove the TextView when our window 4577 // loses focus. 4578 @Override 4579 public void onWindowFocusChanged(boolean hasWindowFocus) { 4580 setActive(hasWindowFocus); 4581 if (hasWindowFocus) { 4582 JWebCoreJavaBridge.setActiveWebView(this); 4583 } else { 4584 JWebCoreJavaBridge.removeActiveWebView(this); 4585 } 4586 super.onWindowFocusChanged(hasWindowFocus); 4587 } 4588 4589 /* 4590 * Pass a message to WebCore Thread, telling the WebCore::Page's 4591 * FocusController to be "inactive" so that it will 4592 * not draw the blinking cursor. It gets set to "active" to draw the cursor 4593 * in WebViewCore.cpp, when the WebCore thread receives key events/clicks. 4594 */ 4595 /* package */ void setFocusControllerInactive() { 4596 // Do not need to also check whether mWebViewCore is null, because 4597 // mNativeClass is only set if mWebViewCore is non null 4598 if (mNativeClass == 0) return; 4599 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0); 4600 } 4601 4602 @Override 4603 protected void onFocusChanged(boolean focused, int direction, 4604 Rect previouslyFocusedRect) { 4605 if (DebugFlags.WEB_VIEW) { 4606 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); 4607 } 4608 if (focused) { 4609 // When we regain focus, if we have window focus, resume drawing 4610 // the cursor ring 4611 if (hasWindowFocus()) { 4612 mDrawCursorRing = true; 4613 if (mNativeClass != 0) { 4614 nativeRecordButtons(true, false, true); 4615 } 4616 //} else { 4617 // The WebView has gained focus while we do not have 4618 // windowfocus. When our window lost focus, we should have 4619 // called nativeRecordButtons(false...) 4620 } 4621 } else { 4622 // When we lost focus, unless focus went to the TextView (which is 4623 // true if we are in editing mode), stop drawing the cursor ring. 4624 if (!inEditingMode()) { 4625 mDrawCursorRing = false; 4626 if (mNativeClass != 0) { 4627 nativeRecordButtons(false, false, true); 4628 } 4629 setFocusControllerInactive(); 4630 } 4631 mGotKeyDown = false; 4632 } 4633 4634 super.onFocusChanged(focused, direction, previouslyFocusedRect); 4635 } 4636 4637 /** 4638 * @hide 4639 */ 4640 @Override 4641 protected boolean setFrame(int left, int top, int right, int bottom) { 4642 boolean changed = super.setFrame(left, top, right, bottom); 4643 if (!changed && mHeightCanMeasure) { 4644 // When mHeightCanMeasure is true, we will set mLastHeightSent to 0 4645 // in WebViewCore after we get the first layout. We do call 4646 // requestLayout() when we get contentSizeChanged(). But the View 4647 // system won't call onSizeChanged if the dimension is not changed. 4648 // In this case, we need to call sendViewSizeZoom() explicitly to 4649 // notify the WebKit about the new dimensions. 4650 sendViewSizeZoom(); 4651 } 4652 return changed; 4653 } 4654 4655 private static class PostScale implements Runnable { 4656 final WebView mWebView; 4657 final boolean mUpdateTextWrap; 4658 4659 public PostScale(WebView webView, boolean updateTextWrap) { 4660 mWebView = webView; 4661 mUpdateTextWrap = updateTextWrap; 4662 } 4663 4664 public void run() { 4665 if (mWebView.mWebViewCore != null) { 4666 // we always force, in case our height changed, in which case we 4667 // still want to send the notification over to webkit. 4668 mWebView.setNewZoomScale(mWebView.mActualScale, 4669 mUpdateTextWrap, true); 4670 // update the zoom buttons as the scale can be changed 4671 if (mWebView.getSettings().getBuiltInZoomControls()) { 4672 mWebView.updateZoomButtonsEnabled(); 4673 } 4674 } 4675 } 4676 } 4677 4678 @Override 4679 protected void onSizeChanged(int w, int h, int ow, int oh) { 4680 super.onSizeChanged(w, h, ow, oh); 4681 // Center zooming to the center of the screen. 4682 if (mZoomScale == 0) { // unless we're already zooming 4683 // To anchor at top left corner. 4684 mZoomCenterX = 0; 4685 mZoomCenterY = getVisibleTitleHeight(); 4686 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 4687 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 4688 } 4689 4690 // adjust the max viewport width depending on the view dimensions. This 4691 // is to ensure the scaling is not going insane. So do not shrink it if 4692 // the view size is temporarily smaller, e.g. when soft keyboard is up. 4693 int newMaxViewportWidth = (int) (Math.max(w, h) / DEFAULT_MIN_ZOOM_SCALE); 4694 if (newMaxViewportWidth > sMaxViewportWidth) { 4695 sMaxViewportWidth = newMaxViewportWidth; 4696 } 4697 4698 // update mMinZoomScale if the minimum zoom scale is not fixed 4699 if (!mMinZoomScaleFixed) { 4700 // when change from narrow screen to wide screen, the new viewWidth 4701 // can be wider than the old content width. We limit the minimum 4702 // scale to 1.0f. The proper minimum scale will be calculated when 4703 // the new picture shows up. 4704 mMinZoomScale = Math.min(1.0f, (float) getViewWidth() 4705 / (mDrawHistory ? mHistoryPicture.getWidth() 4706 : mZoomOverviewWidth)); 4707 if (mInitialScaleInPercent > 0) { 4708 // limit the minZoomScale to the initialScale if it is set 4709 float initialScale = mInitialScaleInPercent / 100.0f; 4710 if (mMinZoomScale > initialScale) { 4711 mMinZoomScale = initialScale; 4712 } 4713 } 4714 } 4715 4716 dismissZoomControl(); 4717 4718 // onSizeChanged() is called during WebView layout. And any 4719 // requestLayout() is blocked during layout. As setNewZoomScale() will 4720 // call its child View to reposition itself through ViewManager's 4721 // scaleAll(), we need to post a Runnable to ensure requestLayout(). 4722 // <b/> 4723 // only update the text wrap scale if width changed. 4724 post(new PostScale(this, w != ow)); 4725 } 4726 4727 @Override 4728 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 4729 super.onScrollChanged(l, t, oldl, oldt); 4730 if (!mInOverScrollMode) { 4731 sendOurVisibleRect(); 4732 // update WebKit if visible title bar height changed. The logic is same 4733 // as getVisibleTitleHeight. 4734 int titleHeight = getTitleHeight(); 4735 if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { 4736 sendViewSizeZoom(); 4737 } 4738 } 4739 } 4740 4741 @Override 4742 public boolean dispatchKeyEvent(KeyEvent event) { 4743 boolean dispatch = true; 4744 4745 // Textfields and plugins need to receive the shift up key even if 4746 // another key was released while the shift key was held down. 4747 if (!inEditingMode() && (mNativeClass == 0 || !nativeFocusIsPlugin())) { 4748 if (event.getAction() == KeyEvent.ACTION_DOWN) { 4749 mGotKeyDown = true; 4750 } else { 4751 if (!mGotKeyDown) { 4752 /* 4753 * We got a key up for which we were not the recipient of 4754 * the original key down. Don't give it to the view. 4755 */ 4756 dispatch = false; 4757 } 4758 mGotKeyDown = false; 4759 } 4760 } 4761 4762 if (dispatch) { 4763 return super.dispatchKeyEvent(event); 4764 } else { 4765 // We didn't dispatch, so let something else handle the key 4766 return false; 4767 } 4768 } 4769 4770 // Here are the snap align logic: 4771 // 1. If it starts nearly horizontally or vertically, snap align; 4772 // 2. If there is a dramitic direction change, let it go; 4773 // 3. If there is a same direction back and forth, lock it. 4774 4775 // adjustable parameters 4776 private int mMinLockSnapReverseDistance; 4777 private static final float MAX_SLOPE_FOR_DIAG = 1.5f; 4778 private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80; 4779 4780 private static int sign(float x) { 4781 return x > 0 ? 1 : (x < 0 ? -1 : 0); 4782 } 4783 4784 // if the page can scroll <= this value, we won't allow the drag tracker 4785 // to have any effect. 4786 private static final int MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER = 4; 4787 4788 private class DragTrackerHandler { 4789 private final DragTracker mProxy; 4790 private final float mStartY, mStartX; 4791 private final float mMinDY, mMinDX; 4792 private final float mMaxDY, mMaxDX; 4793 private float mCurrStretchY, mCurrStretchX; 4794 private int mSX, mSY; 4795 private Interpolator mInterp; 4796 private float[] mXY = new float[2]; 4797 4798 // inner (non-state) classes can't have enums :( 4799 private static final int DRAGGING_STATE = 0; 4800 private static final int ANIMATING_STATE = 1; 4801 private static final int FINISHED_STATE = 2; 4802 private int mState; 4803 4804 public DragTrackerHandler(float x, float y, DragTracker proxy) { 4805 mProxy = proxy; 4806 4807 int docBottom = computeRealVerticalScrollRange() + getTitleHeight(); 4808 int viewTop = getScrollY(); 4809 int viewBottom = viewTop + getHeight(); 4810 4811 mStartY = y; 4812 mMinDY = -viewTop; 4813 mMaxDY = docBottom - viewBottom; 4814 4815 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4816 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " dragtracker y= " + y + 4817 " up/down= " + mMinDY + " " + mMaxDY); 4818 } 4819 4820 int docRight = computeRealHorizontalScrollRange(); 4821 int viewLeft = getScrollX(); 4822 int viewRight = viewLeft + getWidth(); 4823 mStartX = x; 4824 mMinDX = -viewLeft; 4825 mMaxDX = docRight - viewRight; 4826 4827 mState = DRAGGING_STATE; 4828 mProxy.onStartDrag(x, y); 4829 4830 // ensure we buildBitmap at least once 4831 mSX = -99999; 4832 } 4833 4834 private float computeStretch(float delta, float min, float max) { 4835 float stretch = 0; 4836 if (max - min > MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER) { 4837 if (delta < min) { 4838 stretch = delta - min; 4839 } else if (delta > max) { 4840 stretch = delta - max; 4841 } 4842 } 4843 return stretch; 4844 } 4845 4846 public void dragTo(float x, float y) { 4847 float sy = computeStretch(mStartY - y, mMinDY, mMaxDY); 4848 float sx = computeStretch(mStartX - x, mMinDX, mMaxDX); 4849 4850 if ((mSnapScrollMode & SNAP_X) != 0) { 4851 sy = 0; 4852 } else if ((mSnapScrollMode & SNAP_Y) != 0) { 4853 sx = 0; 4854 } 4855 4856 if (mCurrStretchX != sx || mCurrStretchY != sy) { 4857 mCurrStretchX = sx; 4858 mCurrStretchY = sy; 4859 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4860 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "---- stretch " + sx + 4861 " " + sy); 4862 } 4863 if (mProxy.onStretchChange(sx, sy)) { 4864 invalidate(); 4865 } 4866 } 4867 } 4868 4869 public void stopDrag() { 4870 final int DURATION = 200; 4871 int now = (int)SystemClock.uptimeMillis(); 4872 mInterp = new Interpolator(2); 4873 mXY[0] = mCurrStretchX; 4874 mXY[1] = mCurrStretchY; 4875 // float[] blend = new float[] { 0.5f, 0, 0.75f, 1 }; 4876 float[] blend = new float[] { 0, 0.5f, 0.75f, 1 }; 4877 mInterp.setKeyFrame(0, now, mXY, blend); 4878 float[] zerozero = new float[] { 0, 0 }; 4879 mInterp.setKeyFrame(1, now + DURATION, zerozero, null); 4880 mState = ANIMATING_STATE; 4881 4882 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4883 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag, starting animation"); 4884 } 4885 } 4886 4887 // Call this after each draw. If it ruturns null, the tracker is done 4888 public boolean isFinished() { 4889 return mState == FINISHED_STATE; 4890 } 4891 4892 private int hiddenHeightOfTitleBar() { 4893 return getTitleHeight() - getVisibleTitleHeight(); 4894 } 4895 4896 // need a way to know if 565 or 8888 is the right config for 4897 // capturing the display and giving it to the drag proxy 4898 private Bitmap.Config offscreenBitmapConfig() { 4899 // hard code 565 for now 4900 return Bitmap.Config.RGB_565; 4901 } 4902 4903 /* If the tracker draws, then this returns true, otherwise it will 4904 return false, and draw nothing. 4905 */ 4906 public boolean draw(Canvas canvas) { 4907 if (mCurrStretchX != 0 || mCurrStretchY != 0) { 4908 int sx = getScrollX(); 4909 int sy = getScrollY() - hiddenHeightOfTitleBar(); 4910 if (mSX != sx || mSY != sy) { 4911 buildBitmap(sx, sy); 4912 mSX = sx; 4913 mSY = sy; 4914 } 4915 4916 if (mState == ANIMATING_STATE) { 4917 Interpolator.Result result = mInterp.timeToValues(mXY); 4918 if (result == Interpolator.Result.FREEZE_END) { 4919 mState = FINISHED_STATE; 4920 return false; 4921 } else { 4922 mProxy.onStretchChange(mXY[0], mXY[1]); 4923 invalidate(); 4924 // fall through to the draw 4925 } 4926 } 4927 int count = canvas.save(Canvas.MATRIX_SAVE_FLAG); 4928 canvas.translate(sx, sy); 4929 mProxy.onDraw(canvas); 4930 canvas.restoreToCount(count); 4931 return true; 4932 } 4933 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4934 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " -- draw false " + 4935 mCurrStretchX + " " + mCurrStretchY); 4936 } 4937 return false; 4938 } 4939 4940 private void buildBitmap(int sx, int sy) { 4941 int w = getWidth(); 4942 int h = getViewHeight(); 4943 Bitmap bm = Bitmap.createBitmap(w, h, offscreenBitmapConfig()); 4944 Canvas canvas = new Canvas(bm); 4945 canvas.translate(-sx, -sy); 4946 drawContent(canvas); 4947 4948 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4949 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "--- buildBitmap " + sx + 4950 " " + sy + " " + w + " " + h); 4951 } 4952 mProxy.onBitmapChange(bm); 4953 } 4954 } 4955 4956 /** @hide */ 4957 public static class DragTracker { 4958 public void onStartDrag(float x, float y) {} 4959 public boolean onStretchChange(float sx, float sy) { 4960 // return true to have us inval the view 4961 return false; 4962 } 4963 public void onStopDrag() {} 4964 public void onBitmapChange(Bitmap bm) {} 4965 public void onDraw(Canvas canvas) {} 4966 } 4967 4968 /** @hide */ 4969 public DragTracker getDragTracker() { 4970 return mDragTracker; 4971 } 4972 4973 /** @hide */ 4974 public void setDragTracker(DragTracker tracker) { 4975 mDragTracker = tracker; 4976 } 4977 4978 private DragTracker mDragTracker; 4979 private DragTrackerHandler mDragTrackerHandler; 4980 4981 private class ScaleDetectorListener implements 4982 ScaleGestureDetector.OnScaleGestureListener { 4983 4984 public boolean onScaleBegin(ScaleGestureDetector detector) { 4985 // cancel the single touch handling 4986 cancelTouch(); 4987 dismissZoomControl(); 4988 // reset the zoom overview mode so that the page won't auto grow 4989 mInZoomOverview = false; 4990 // If it is in password mode, turn it off so it does not draw 4991 // misplaced. 4992 if (inEditingMode() && nativeFocusCandidateIsPassword()) { 4993 mWebTextView.setInPassword(false); 4994 } 4995 4996 mViewManager.startZoom(); 4997 4998 return true; 4999 } 5000 5001 public void onScaleEnd(ScaleGestureDetector detector) { 5002 if (mPreviewZoomOnly) { 5003 mPreviewZoomOnly = false; 5004 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 5005 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 5006 // don't reflow when zoom in; when zoom out, do reflow if the 5007 // new scale is almost minimum scale; 5008 boolean reflowNow = (mActualScale - mMinZoomScale 5009 <= MINIMUM_SCALE_INCREMENT) 5010 || ((mActualScale <= 0.8 * mTextWrapScale)); 5011 // force zoom after mPreviewZoomOnly is set to false so that the 5012 // new view size will be passed to the WebKit 5013 setNewZoomScale(mActualScale, reflowNow, true); 5014 // call invalidate() to draw without zoom filter 5015 invalidate(); 5016 } 5017 // adjust the edit text view if needed 5018 if (inEditingMode() && didUpdateTextViewBounds(false) 5019 && nativeFocusCandidateIsPassword()) { 5020 // If it is a password field, start drawing the 5021 // WebTextView once again. 5022 mWebTextView.setInPassword(true); 5023 } 5024 // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it 5025 // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it 5026 // may trigger the unwanted fling. 5027 mTouchMode = TOUCH_PINCH_DRAG; 5028 mConfirmMove = true; 5029 startTouch(detector.getFocusX(), detector.getFocusY(), 5030 mLastTouchTime); 5031 5032 mViewManager.endZoom(); 5033 } 5034 5035 public boolean onScale(ScaleGestureDetector detector) { 5036 float scale = (float) (Math.round(detector.getScaleFactor() 5037 * mActualScale * 100) / 100.0); 5038 if (Math.abs(scale - mActualScale) >= MINIMUM_SCALE_INCREMENT) { 5039 mPreviewZoomOnly = true; 5040 // limit the scale change per step 5041 if (scale > mActualScale) { 5042 scale = Math.min(scale, mActualScale * 1.25f); 5043 } else { 5044 scale = Math.max(scale, mActualScale * 0.8f); 5045 } 5046 mZoomCenterX = detector.getFocusX(); 5047 mZoomCenterY = detector.getFocusY(); 5048 setNewZoomScale(scale, false, false); 5049 invalidate(); 5050 return true; 5051 } 5052 return false; 5053 } 5054 } 5055 5056 private boolean hitFocusedPlugin(int contentX, int contentY) { 5057 if (DebugFlags.WEB_VIEW) { 5058 Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin()); 5059 Rect r = nativeFocusNodeBounds(); 5060 Log.v(LOGTAG, "nativeFocusNodeBounds()=(" + r.left + ", " + r.top 5061 + ", " + r.right + ", " + r.bottom + ")"); 5062 } 5063 return nativeFocusIsPlugin() 5064 && nativeFocusNodeBounds().contains(contentX, contentY); 5065 } 5066 5067 private boolean shouldForwardTouchEvent() { 5068 return mFullScreenHolder != null || (mForwardTouchEvents 5069 && !mSelectingText 5070 && mPreventDefault != PREVENT_DEFAULT_IGNORE); 5071 } 5072 5073 private boolean inFullScreenMode() { 5074 return mFullScreenHolder != null; 5075 } 5076 5077 @Override 5078 public boolean onTouchEvent(MotionEvent ev) { 5079 if (mNativeClass == 0 || (!isClickable() && !isLongClickable())) { 5080 return false; 5081 } 5082 5083 if (DebugFlags.WEB_VIEW) { 5084 Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" 5085 + mTouchMode); 5086 } 5087 5088 int action; 5089 float x, y; 5090 long eventTime = ev.getEventTime(); 5091 5092 // FIXME: we may consider to give WebKit an option to handle multi-touch 5093 // events later. 5094 if (mSupportMultiTouch && ev.getPointerCount() > 1) { 5095 if (mAllowPanAndScale || mMinZoomScale < mMaxZoomScale) { 5096 mScaleDetector.onTouchEvent(ev); 5097 if (mScaleDetector.isInProgress()) { 5098 mLastTouchTime = eventTime; 5099 if (!mAllowPanAndScale) { 5100 return true; 5101 } 5102 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5103 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5104 } 5105 x = mScaleDetector.getFocusX(); 5106 y = mScaleDetector.getFocusY(); 5107 action = ev.getAction() & MotionEvent.ACTION_MASK; 5108 if (action == MotionEvent.ACTION_POINTER_DOWN) { 5109 cancelTouch(); 5110 action = MotionEvent.ACTION_DOWN; 5111 } else if (action == MotionEvent.ACTION_POINTER_UP) { 5112 // set mLastTouchX/Y to the remaining point 5113 mLastTouchX = x; 5114 mLastTouchY = y; 5115 } else if (action == MotionEvent.ACTION_MOVE) { 5116 // negative x or y indicate it is on the edge, skip it. 5117 if (x < 0 || y < 0) { 5118 return true; 5119 } 5120 } 5121 } else { 5122 // if the page disallow zoom, skip multi-pointer action 5123 return true; 5124 } 5125 } else { 5126 action = ev.getAction(); 5127 x = ev.getX(); 5128 y = ev.getY(); 5129 } 5130 5131 // Due to the touch screen edge effect, a touch closer to the edge 5132 // always snapped to the edge. As getViewWidth() can be different from 5133 // getWidth() due to the scrollbar, adjusting the point to match 5134 // getViewWidth(). Same applied to the height. 5135 if (x > getViewWidth() - 1) { 5136 x = getViewWidth() - 1; 5137 } 5138 if (y > getViewHeightWithTitle() - 1) { 5139 y = getViewHeightWithTitle() - 1; 5140 } 5141 5142 float fDeltaX = mLastTouchX - x; 5143 float fDeltaY = mLastTouchY - y; 5144 int deltaX = (int) fDeltaX; 5145 int deltaY = (int) fDeltaY; 5146 int contentX = viewToContentX((int) x + mScrollX); 5147 int contentY = viewToContentY((int) y + mScrollY); 5148 5149 switch (action) { 5150 case MotionEvent.ACTION_DOWN: { 5151 mPreventDefault = PREVENT_DEFAULT_NO; 5152 mConfirmMove = false; 5153 if (!mScroller.isFinished()) { 5154 // stop the current scroll animation, but if this is 5155 // the start of a fling, allow it to add to the current 5156 // fling's velocity 5157 mScroller.abortAnimation(); 5158 mTouchMode = TOUCH_DRAG_START_MODE; 5159 mConfirmMove = true; 5160 mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); 5161 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { 5162 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); 5163 if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { 5164 mTouchMode = TOUCH_DOUBLE_TAP_MODE; 5165 } else { 5166 // commit the short press action for the previous tap 5167 doShortPress(); 5168 mTouchMode = TOUCH_INIT_MODE; 5169 mDeferTouchProcess = (!inFullScreenMode() 5170 && mForwardTouchEvents) ? hitFocusedPlugin( 5171 contentX, contentY) : false; 5172 } 5173 } else { // the normal case 5174 mPreviewZoomOnly = false; 5175 mTouchMode = TOUCH_INIT_MODE; 5176 mDeferTouchProcess = (!inFullScreenMode() 5177 && mForwardTouchEvents) ? hitFocusedPlugin( 5178 contentX, contentY) : false; 5179 mWebViewCore.sendMessage( 5180 EventHub.UPDATE_FRAME_CACHE_IF_LOADING); 5181 if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { 5182 EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION, 5183 (eventTime - mLastTouchUpTime), eventTime); 5184 } 5185 if (mSelectingText) { 5186 mDrawSelectionPointer = false; 5187 mSelectionStarted = nativeStartSelection(contentX, contentY); 5188 if (DebugFlags.WEB_VIEW) { 5189 Log.v(LOGTAG, "select=" + contentX + "," + contentY); 5190 } 5191 invalidate(); 5192 } 5193 } 5194 // Trigger the link 5195 if (mTouchMode == TOUCH_INIT_MODE 5196 || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 5197 mPrivateHandler.sendEmptyMessageDelayed( 5198 SWITCH_TO_SHORTPRESS, TAP_TIMEOUT); 5199 mPrivateHandler.sendEmptyMessageDelayed( 5200 SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT); 5201 if (inFullScreenMode() || mDeferTouchProcess) { 5202 mPreventDefault = PREVENT_DEFAULT_YES; 5203 } else if (mForwardTouchEvents) { 5204 mPreventDefault = PREVENT_DEFAULT_MAYBE_YES; 5205 } else { 5206 mPreventDefault = PREVENT_DEFAULT_NO; 5207 } 5208 // pass the touch events from UI thread to WebCore thread 5209 if (shouldForwardTouchEvent()) { 5210 TouchEventData ted = new TouchEventData(); 5211 ted.mAction = action; 5212 ted.mX = contentX; 5213 ted.mY = contentY; 5214 ted.mMetaState = ev.getMetaState(); 5215 ted.mReprocess = mDeferTouchProcess; 5216 if (mDeferTouchProcess) { 5217 // still needs to set them for compute deltaX/Y 5218 mLastTouchX = x; 5219 mLastTouchY = y; 5220 ted.mViewX = x; 5221 ted.mViewY = y; 5222 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5223 break; 5224 } 5225 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5226 if (!inFullScreenMode()) { 5227 mPrivateHandler.sendMessageDelayed(mPrivateHandler 5228 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 5229 action, 0), TAP_TIMEOUT); 5230 } 5231 } 5232 } 5233 startTouch(x, y, eventTime); 5234 break; 5235 } 5236 case MotionEvent.ACTION_MOVE: { 5237 boolean firstMove = false; 5238 if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY) 5239 >= mTouchSlopSquare) { 5240 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5241 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5242 mConfirmMove = true; 5243 firstMove = true; 5244 if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 5245 mTouchMode = TOUCH_INIT_MODE; 5246 } 5247 } 5248 // pass the touch events from UI thread to WebCore thread 5249 if (shouldForwardTouchEvent() && mConfirmMove && (firstMove 5250 || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) { 5251 mLastSentTouchTime = eventTime; 5252 TouchEventData ted = new TouchEventData(); 5253 ted.mAction = action; 5254 ted.mX = contentX; 5255 ted.mY = contentY; 5256 ted.mMetaState = ev.getMetaState(); 5257 ted.mReprocess = mDeferTouchProcess; 5258 if (mDeferTouchProcess) { 5259 ted.mViewX = x; 5260 ted.mViewY = y; 5261 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5262 break; 5263 } 5264 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5265 if (firstMove && !inFullScreenMode()) { 5266 mPrivateHandler.sendMessageDelayed(mPrivateHandler 5267 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 5268 action, 0), TAP_TIMEOUT); 5269 } 5270 } 5271 if (mTouchMode == TOUCH_DONE_MODE 5272 || mPreventDefault == PREVENT_DEFAULT_YES) { 5273 // no dragging during scroll zoom animation, or when prevent 5274 // default is yes 5275 break; 5276 } 5277 if (mVelocityTracker == null) { 5278 Log.e(LOGTAG, "Got null mVelocityTracker when " 5279 + "mPreventDefault = " + mPreventDefault 5280 + " mDeferTouchProcess = " + mDeferTouchProcess 5281 + " mTouchMode = " + mTouchMode); 5282 } 5283 mVelocityTracker.addMovement(ev); 5284 if (mSelectingText && mSelectionStarted) { 5285 if (DebugFlags.WEB_VIEW) { 5286 Log.v(LOGTAG, "extend=" + contentX + "," + contentY); 5287 } 5288 nativeExtendSelection(contentX, contentY); 5289 invalidate(); 5290 break; 5291 } 5292 5293 if (mTouchMode != TOUCH_DRAG_MODE) { 5294 5295 if (!mConfirmMove) { 5296 break; 5297 } 5298 if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES 5299 || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 5300 // track mLastTouchTime as we may need to do fling at 5301 // ACTION_UP 5302 mLastTouchTime = eventTime; 5303 break; 5304 } 5305 5306 // Only lock dragging to one axis if we don't have a scale in progress. 5307 // Scaling implies free-roaming movement. Note we'll only ever get here 5308 // if mAllowPanAndScale is true. 5309 if (mScaleDetector != null && !mScaleDetector.isInProgress()) { 5310 // if it starts nearly horizontal or vertical, enforce it 5311 int ax = Math.abs(deltaX); 5312 int ay = Math.abs(deltaY); 5313 if (ax > MAX_SLOPE_FOR_DIAG * ay) { 5314 mSnapScrollMode = SNAP_X; 5315 mSnapPositive = deltaX > 0; 5316 } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { 5317 mSnapScrollMode = SNAP_Y; 5318 mSnapPositive = deltaY > 0; 5319 } 5320 } 5321 5322 mTouchMode = TOUCH_DRAG_MODE; 5323 mLastTouchX = x; 5324 mLastTouchY = y; 5325 fDeltaX = 0.0f; 5326 fDeltaY = 0.0f; 5327 deltaX = 0; 5328 deltaY = 0; 5329 5330 startDrag(); 5331 } 5332 5333 if (mDragTrackerHandler != null) { 5334 mDragTrackerHandler.dragTo(x, y); 5335 } 5336 5337 // do pan 5338 boolean done = false; 5339 boolean keepScrollBarsVisible = false; 5340 if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) { 5341 mLastTouchX = x; 5342 mLastTouchY = y; 5343 keepScrollBarsVisible = done = true; 5344 } else { 5345 if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) { 5346 int ax = Math.abs(deltaX); 5347 int ay = Math.abs(deltaY); 5348 if (mSnapScrollMode == SNAP_X) { 5349 // radical change means getting out of snap mode 5350 if (ay > MAX_SLOPE_FOR_DIAG * ax 5351 && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) { 5352 mSnapScrollMode = SNAP_NONE; 5353 } 5354 // reverse direction means lock in the snap mode 5355 if (ax > MAX_SLOPE_FOR_DIAG * ay && 5356 (mSnapPositive 5357 ? deltaX < -mMinLockSnapReverseDistance 5358 : deltaX > mMinLockSnapReverseDistance)) { 5359 mSnapScrollMode |= SNAP_LOCK; 5360 } 5361 } else { 5362 // radical change means getting out of snap mode 5363 if (ax > MAX_SLOPE_FOR_DIAG * ay 5364 && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) { 5365 mSnapScrollMode = SNAP_NONE; 5366 } 5367 // reverse direction means lock in the snap mode 5368 if (ay > MAX_SLOPE_FOR_DIAG * ax && 5369 (mSnapPositive 5370 ? deltaY < -mMinLockSnapReverseDistance 5371 : deltaY > mMinLockSnapReverseDistance)) { 5372 mSnapScrollMode |= SNAP_LOCK; 5373 } 5374 } 5375 } 5376 if (mSnapScrollMode != SNAP_NONE) { 5377 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 5378 deltaY = 0; 5379 } else { 5380 deltaX = 0; 5381 } 5382 } 5383 if ((deltaX | deltaY) != 0) { 5384 if (deltaX != 0) { 5385 mLastTouchX = x; 5386 } 5387 if (deltaY != 0) { 5388 mLastTouchY = y; 5389 } 5390 mHeldMotionless = MOTIONLESS_FALSE; 5391 } else { 5392 // keep the scrollbar on the screen even there is no 5393 // scroll 5394 mLastTouchX = x; 5395 mLastTouchY = y; 5396 keepScrollBarsVisible = true; 5397 } 5398 mLastTouchTime = eventTime; 5399 mUserScroll = true; 5400 } 5401 5402 doDrag(deltaX, deltaY); 5403 5404 if (keepScrollBarsVisible) { 5405 if (mHeldMotionless != MOTIONLESS_TRUE) { 5406 mHeldMotionless = MOTIONLESS_TRUE; 5407 invalidate(); 5408 } 5409 // keep the scrollbar on the screen even there is no scroll 5410 awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), 5411 false); 5412 // return false to indicate that we can't pan out of the 5413 // view space 5414 return !done; 5415 } 5416 break; 5417 } 5418 case MotionEvent.ACTION_UP: { 5419 // pass the touch events from UI thread to WebCore thread 5420 if (shouldForwardTouchEvent()) { 5421 TouchEventData ted = new TouchEventData(); 5422 ted.mAction = action; 5423 ted.mX = contentX; 5424 ted.mY = contentY; 5425 ted.mMetaState = ev.getMetaState(); 5426 ted.mReprocess = mDeferTouchProcess; 5427 if (mDeferTouchProcess) { 5428 ted.mViewX = x; 5429 ted.mViewY = y; 5430 } 5431 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5432 } 5433 mLastTouchUpTime = eventTime; 5434 switch (mTouchMode) { 5435 case TOUCH_DOUBLE_TAP_MODE: // double tap 5436 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5437 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5438 if (inFullScreenMode() || mDeferTouchProcess) { 5439 TouchEventData ted = new TouchEventData(); 5440 ted.mAction = WebViewCore.ACTION_DOUBLETAP; 5441 ted.mX = contentX; 5442 ted.mY = contentY; 5443 ted.mMetaState = ev.getMetaState(); 5444 ted.mReprocess = mDeferTouchProcess; 5445 if (mDeferTouchProcess) { 5446 ted.mViewX = x; 5447 ted.mViewY = y; 5448 } 5449 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5450 } else if (mPreventDefault != PREVENT_DEFAULT_YES){ 5451 doDoubleTap(); 5452 mTouchMode = TOUCH_DONE_MODE; 5453 } 5454 break; 5455 case TOUCH_INIT_MODE: // tap 5456 case TOUCH_SHORTPRESS_START_MODE: 5457 case TOUCH_SHORTPRESS_MODE: 5458 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5459 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5460 if (mConfirmMove) { 5461 Log.w(LOGTAG, "Miss a drag as we are waiting for" + 5462 " WebCore's response for touch down."); 5463 if (mPreventDefault != PREVENT_DEFAULT_YES 5464 && (computeMaxScrollX() > 0 5465 || computeMaxScrollY() > 0)) { 5466 // UI takes control back, cancel WebCore touch 5467 cancelWebCoreTouchEvent(contentX, contentY, 5468 true); 5469 // we will not rewrite drag code here, but we 5470 // will try fling if it applies. 5471 WebViewCore.reducePriority(); 5472 // to get better performance, pause updating the 5473 // picture 5474 WebViewCore.pauseUpdatePicture(mWebViewCore); 5475 // fall through to TOUCH_DRAG_MODE 5476 } else { 5477 // WebKit may consume the touch event and modify 5478 // DOM. drawContentPicture() will be called with 5479 // animateSroll as true for better performance. 5480 // Force redraw in high-quality. 5481 invalidate(); 5482 break; 5483 } 5484 } else { 5485 if (mSelectingText) { 5486 if (nativeHitSelection(contentX, contentY)) { 5487 copySelection(); 5488 } 5489 selectionDone(); 5490 break; 5491 } 5492 if (mTouchMode == TOUCH_INIT_MODE) { 5493 mPrivateHandler.sendEmptyMessageDelayed( 5494 RELEASE_SINGLE_TAP, ViewConfiguration 5495 .getDoubleTapTimeout()); 5496 } else { 5497 doShortPress(); 5498 } 5499 break; 5500 } 5501 case TOUCH_DRAG_MODE: 5502 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 5503 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 5504 // if the user waits a while w/o moving before the 5505 // up, we don't want to do a fling 5506 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { 5507 if (mVelocityTracker == null) { 5508 Log.e(LOGTAG, "Got null mVelocityTracker when " 5509 + "mPreventDefault = " 5510 + mPreventDefault 5511 + " mDeferTouchProcess = " 5512 + mDeferTouchProcess); 5513 } 5514 mVelocityTracker.addMovement(ev); 5515 // set to MOTIONLESS_IGNORE so that it won't keep 5516 // removing and sending message in 5517 // drawCoreAndCursorRing() 5518 mHeldMotionless = MOTIONLESS_IGNORE; 5519 doFling(); 5520 break; 5521 } else { 5522 if (mScroller.springBack(mScrollX, mScrollY, 0, 5523 computeMaxScrollX(), 0, 5524 computeMaxScrollY())) { 5525 invalidate(); 5526 } 5527 } 5528 // redraw in high-quality, as we're done dragging 5529 mHeldMotionless = MOTIONLESS_TRUE; 5530 invalidate(); 5531 // fall through 5532 case TOUCH_DRAG_START_MODE: 5533 // TOUCH_DRAG_START_MODE should not happen for the real 5534 // device as we almost certain will get a MOVE. But this 5535 // is possible on emulator. 5536 mLastVelocity = 0; 5537 WebViewCore.resumePriority(); 5538 WebViewCore.resumeUpdatePicture(mWebViewCore); 5539 break; 5540 } 5541 stopTouch(); 5542 break; 5543 } 5544 case MotionEvent.ACTION_CANCEL: { 5545 if (mTouchMode == TOUCH_DRAG_MODE) { 5546 mScroller.springBack(mScrollX, mScrollY, 0, 5547 computeMaxScrollX(), 0, computeMaxScrollY()); 5548 invalidate(); 5549 } 5550 cancelWebCoreTouchEvent(contentX, contentY, false); 5551 cancelTouch(); 5552 break; 5553 } 5554 } 5555 return true; 5556 } 5557 5558 private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) { 5559 if (shouldForwardTouchEvent()) { 5560 if (removeEvents) { 5561 mWebViewCore.removeMessages(EventHub.TOUCH_EVENT); 5562 } 5563 TouchEventData ted = new TouchEventData(); 5564 ted.mX = x; 5565 ted.mY = y; 5566 ted.mAction = MotionEvent.ACTION_CANCEL; 5567 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5568 mPreventDefault = PREVENT_DEFAULT_IGNORE; 5569 } 5570 } 5571 5572 private void startTouch(float x, float y, long eventTime) { 5573 // Remember where the motion event started 5574 mLastTouchX = x; 5575 mLastTouchY = y; 5576 mLastTouchTime = eventTime; 5577 mVelocityTracker = VelocityTracker.obtain(); 5578 mSnapScrollMode = SNAP_NONE; 5579 if (mDragTracker != null) { 5580 mDragTrackerHandler = new DragTrackerHandler(x, y, mDragTracker); 5581 } 5582 } 5583 5584 private void startDrag() { 5585 WebViewCore.reducePriority(); 5586 // to get better performance, pause updating the picture 5587 WebViewCore.pauseUpdatePicture(mWebViewCore); 5588 if (!mDragFromTextInput) { 5589 nativeHideCursor(); 5590 } 5591 WebSettings settings = getSettings(); 5592 if (settings.supportZoom() 5593 && settings.getBuiltInZoomControls() 5594 && !getZoomButtonsController().isVisible() 5595 && mMinZoomScale < mMaxZoomScale 5596 && (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF 5597 || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF)) { 5598 mZoomButtonsController.setVisible(true); 5599 int count = settings.getDoubleTapToastCount(); 5600 if (mInZoomOverview && count > 0) { 5601 settings.setDoubleTapToastCount(--count); 5602 Toast.makeText(mContext, 5603 com.android.internal.R.string.double_tap_toast, 5604 Toast.LENGTH_LONG).show(); 5605 } 5606 } 5607 } 5608 5609 private void doDrag(int deltaX, int deltaY) { 5610 if ((deltaX | deltaY) != 0) { 5611 final int oldX = mScrollX; 5612 final int oldY = mScrollY; 5613 final int rangeX = computeMaxScrollX(); 5614 final int rangeY = computeMaxScrollY(); 5615 5616 if (mEdgeGlowTop != null) { 5617 // Save the deltas for overscroll glow. 5618 mOverscrollDeltaX = deltaX; 5619 mOverscrollDeltaY = deltaY; 5620 } 5621 5622 overScrollBy(deltaX, deltaY, oldX, oldY, 5623 rangeX, rangeY, 5624 mOverscrollDistance, mOverscrollDistance, true); 5625 if (mEdgeGlowTop != null && 5626 (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() || 5627 !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) { 5628 invalidate(); 5629 } 5630 } 5631 if (!getSettings().getBuiltInZoomControls()) { 5632 boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; 5633 if (mZoomControls != null && showPlusMinus) { 5634 if (mZoomControls.getVisibility() == View.VISIBLE) { 5635 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 5636 } else { 5637 mZoomControls.show(showPlusMinus, false); 5638 } 5639 mPrivateHandler.postDelayed(mZoomControlRunnable, 5640 ZOOM_CONTROLS_TIMEOUT); 5641 } 5642 } 5643 } 5644 5645 private void stopTouch() { 5646 if (mDragTrackerHandler != null) { 5647 mDragTrackerHandler.stopDrag(); 5648 } 5649 // we also use mVelocityTracker == null to tell us that we are 5650 // not "moving around", so we can take the slower/prettier 5651 // mode in the drawing code 5652 if (mVelocityTracker != null) { 5653 mVelocityTracker.recycle(); 5654 mVelocityTracker = null; 5655 } 5656 5657 // Release any pulled glows 5658 if (mEdgeGlowTop != null) { 5659 mEdgeGlowTop.onRelease(); 5660 mEdgeGlowBottom.onRelease(); 5661 mEdgeGlowLeft.onRelease(); 5662 mEdgeGlowRight.onRelease(); 5663 } 5664 } 5665 5666 private void cancelTouch() { 5667 if (mDragTrackerHandler != null) { 5668 mDragTrackerHandler.stopDrag(); 5669 } 5670 // we also use mVelocityTracker == null to tell us that we are 5671 // not "moving around", so we can take the slower/prettier 5672 // mode in the drawing code 5673 if (mVelocityTracker != null) { 5674 mVelocityTracker.recycle(); 5675 mVelocityTracker = null; 5676 } 5677 5678 // Release any pulled glows 5679 if (mEdgeGlowTop != null) { 5680 mEdgeGlowTop.onRelease(); 5681 mEdgeGlowBottom.onRelease(); 5682 mEdgeGlowLeft.onRelease(); 5683 mEdgeGlowRight.onRelease(); 5684 } 5685 5686 if (mTouchMode == TOUCH_DRAG_MODE) { 5687 WebViewCore.resumePriority(); 5688 WebViewCore.resumeUpdatePicture(mWebViewCore); 5689 } 5690 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5691 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5692 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 5693 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 5694 mHeldMotionless = MOTIONLESS_TRUE; 5695 mTouchMode = TOUCH_DONE_MODE; 5696 nativeHideCursor(); 5697 } 5698 5699 private long mTrackballFirstTime = 0; 5700 private long mTrackballLastTime = 0; 5701 private float mTrackballRemainsX = 0.0f; 5702 private float mTrackballRemainsY = 0.0f; 5703 private int mTrackballXMove = 0; 5704 private int mTrackballYMove = 0; 5705 private boolean mSelectingText = false; 5706 private boolean mSelectionStarted = false; 5707 private boolean mExtendSelection = false; 5708 private boolean mDrawSelectionPointer = false; 5709 private static final int TRACKBALL_KEY_TIMEOUT = 1000; 5710 private static final int TRACKBALL_TIMEOUT = 200; 5711 private static final int TRACKBALL_WAIT = 100; 5712 private static final int TRACKBALL_SCALE = 400; 5713 private static final int TRACKBALL_SCROLL_COUNT = 5; 5714 private static final int TRACKBALL_MOVE_COUNT = 10; 5715 private static final int TRACKBALL_MULTIPLIER = 3; 5716 private static final int SELECT_CURSOR_OFFSET = 16; 5717 private int mSelectX = 0; 5718 private int mSelectY = 0; 5719 private boolean mFocusSizeChanged = false; 5720 private boolean mShiftIsPressed = false; 5721 private boolean mTrackballDown = false; 5722 private long mTrackballUpTime = 0; 5723 private long mLastCursorTime = 0; 5724 private Rect mLastCursorBounds; 5725 5726 // Set by default; BrowserActivity clears to interpret trackball data 5727 // directly for movement. Currently, the framework only passes 5728 // arrow key events, not trackball events, from one child to the next 5729 private boolean mMapTrackballToArrowKeys = true; 5730 5731 public void setMapTrackballToArrowKeys(boolean setMap) { 5732 mMapTrackballToArrowKeys = setMap; 5733 } 5734 5735 void resetTrackballTime() { 5736 mTrackballLastTime = 0; 5737 } 5738 5739 @Override 5740 public boolean onTrackballEvent(MotionEvent ev) { 5741 long time = ev.getEventTime(); 5742 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) { 5743 if (ev.getY() > 0) pageDown(true); 5744 if (ev.getY() < 0) pageUp(true); 5745 return true; 5746 } 5747 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 5748 if (mSelectingText) { 5749 return true; // discard press if copy in progress 5750 } 5751 mTrackballDown = true; 5752 if (mNativeClass == 0) { 5753 return false; 5754 } 5755 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 5756 if (time - mLastCursorTime <= TRACKBALL_TIMEOUT 5757 && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { 5758 nativeSelectBestAt(mLastCursorBounds); 5759 } 5760 if (DebugFlags.WEB_VIEW) { 5761 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev 5762 + " time=" + time 5763 + " mLastCursorTime=" + mLastCursorTime); 5764 } 5765 if (isInTouchMode()) requestFocusFromTouch(); 5766 return false; // let common code in onKeyDown at it 5767 } 5768 if (ev.getAction() == MotionEvent.ACTION_UP) { 5769 // LONG_PRESS_CENTER is set in common onKeyDown 5770 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 5771 mTrackballDown = false; 5772 mTrackballUpTime = time; 5773 if (mSelectingText) { 5774 if (mExtendSelection) { 5775 copySelection(); 5776 selectionDone(); 5777 } else { 5778 mExtendSelection = true; 5779 nativeSetExtendSelection(); 5780 invalidate(); // draw the i-beam instead of the arrow 5781 } 5782 return true; // discard press if copy in progress 5783 } 5784 if (DebugFlags.WEB_VIEW) { 5785 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev 5786 + " time=" + time 5787 ); 5788 } 5789 return false; // let common code in onKeyUp at it 5790 } 5791 if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { 5792 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); 5793 return false; 5794 } 5795 if (mTrackballDown) { 5796 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit"); 5797 return true; // discard move if trackball is down 5798 } 5799 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { 5800 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); 5801 return true; 5802 } 5803 // TODO: alternatively we can do panning as touch does 5804 switchOutDrawHistory(); 5805 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { 5806 if (DebugFlags.WEB_VIEW) { 5807 Log.v(LOGTAG, "onTrackballEvent time=" 5808 + time + " last=" + mTrackballLastTime); 5809 } 5810 mTrackballFirstTime = time; 5811 mTrackballXMove = mTrackballYMove = 0; 5812 } 5813 mTrackballLastTime = time; 5814 if (DebugFlags.WEB_VIEW) { 5815 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); 5816 } 5817 mTrackballRemainsX += ev.getX(); 5818 mTrackballRemainsY += ev.getY(); 5819 doTrackball(time); 5820 return true; 5821 } 5822 5823 void moveSelection(float xRate, float yRate) { 5824 if (mNativeClass == 0) 5825 return; 5826 int width = getViewWidth(); 5827 int height = getViewHeight(); 5828 mSelectX += xRate; 5829 mSelectY += yRate; 5830 int maxX = width + mScrollX; 5831 int maxY = height + mScrollY; 5832 mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET 5833 , mSelectX)); 5834 mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET 5835 , mSelectY)); 5836 if (DebugFlags.WEB_VIEW) { 5837 Log.v(LOGTAG, "moveSelection" 5838 + " mSelectX=" + mSelectX 5839 + " mSelectY=" + mSelectY 5840 + " mScrollX=" + mScrollX 5841 + " mScrollY=" + mScrollY 5842 + " xRate=" + xRate 5843 + " yRate=" + yRate 5844 ); 5845 } 5846 nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY)); 5847 int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET 5848 : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 5849 : 0; 5850 int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET 5851 : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 5852 : 0; 5853 pinScrollBy(scrollX, scrollY, true, 0); 5854 Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); 5855 requestRectangleOnScreen(select); 5856 invalidate(); 5857 } 5858 5859 private int scaleTrackballX(float xRate, int width) { 5860 int xMove = (int) (xRate / TRACKBALL_SCALE * width); 5861 int nextXMove = xMove; 5862 if (xMove > 0) { 5863 if (xMove > mTrackballXMove) { 5864 xMove -= mTrackballXMove; 5865 } 5866 } else if (xMove < mTrackballXMove) { 5867 xMove -= mTrackballXMove; 5868 } 5869 mTrackballXMove = nextXMove; 5870 return xMove; 5871 } 5872 5873 private int scaleTrackballY(float yRate, int height) { 5874 int yMove = (int) (yRate / TRACKBALL_SCALE * height); 5875 int nextYMove = yMove; 5876 if (yMove > 0) { 5877 if (yMove > mTrackballYMove) { 5878 yMove -= mTrackballYMove; 5879 } 5880 } else if (yMove < mTrackballYMove) { 5881 yMove -= mTrackballYMove; 5882 } 5883 mTrackballYMove = nextYMove; 5884 return yMove; 5885 } 5886 5887 private int keyCodeToSoundsEffect(int keyCode) { 5888 switch(keyCode) { 5889 case KeyEvent.KEYCODE_DPAD_UP: 5890 return SoundEffectConstants.NAVIGATION_UP; 5891 case KeyEvent.KEYCODE_DPAD_RIGHT: 5892 return SoundEffectConstants.NAVIGATION_RIGHT; 5893 case KeyEvent.KEYCODE_DPAD_DOWN: 5894 return SoundEffectConstants.NAVIGATION_DOWN; 5895 case KeyEvent.KEYCODE_DPAD_LEFT: 5896 return SoundEffectConstants.NAVIGATION_LEFT; 5897 } 5898 throw new IllegalArgumentException("keyCode must be one of " + 5899 "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + 5900 "KEYCODE_DPAD_LEFT}."); 5901 } 5902 5903 private void doTrackball(long time) { 5904 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime); 5905 if (elapsed == 0) { 5906 elapsed = TRACKBALL_TIMEOUT; 5907 } 5908 float xRate = mTrackballRemainsX * 1000 / elapsed; 5909 float yRate = mTrackballRemainsY * 1000 / elapsed; 5910 int viewWidth = getViewWidth(); 5911 int viewHeight = getViewHeight(); 5912 if (mSelectingText) { 5913 if (!mDrawSelectionPointer) { 5914 // The last selection was made by touch, disabling drawing the 5915 // selection pointer. Allow the trackball to adjust the 5916 // position of the touch control. 5917 mSelectX = contentToViewX(nativeSelectionX()); 5918 mSelectY = contentToViewY(nativeSelectionY()); 5919 mDrawSelectionPointer = mExtendSelection = true; 5920 nativeSetExtendSelection(); 5921 } 5922 moveSelection(scaleTrackballX(xRate, viewWidth), 5923 scaleTrackballY(yRate, viewHeight)); 5924 mTrackballRemainsX = mTrackballRemainsY = 0; 5925 return; 5926 } 5927 float ax = Math.abs(xRate); 5928 float ay = Math.abs(yRate); 5929 float maxA = Math.max(ax, ay); 5930 if (DebugFlags.WEB_VIEW) { 5931 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed 5932 + " xRate=" + xRate 5933 + " yRate=" + yRate 5934 + " mTrackballRemainsX=" + mTrackballRemainsX 5935 + " mTrackballRemainsY=" + mTrackballRemainsY); 5936 } 5937 int width = mContentWidth - viewWidth; 5938 int height = mContentHeight - viewHeight; 5939 if (width < 0) width = 0; 5940 if (height < 0) height = 0; 5941 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); 5942 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); 5943 maxA = Math.max(ax, ay); 5944 int count = Math.max(0, (int) maxA); 5945 int oldScrollX = mScrollX; 5946 int oldScrollY = mScrollY; 5947 if (count > 0) { 5948 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? 5949 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : 5950 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : 5951 KeyEvent.KEYCODE_DPAD_RIGHT; 5952 count = Math.min(count, TRACKBALL_MOVE_COUNT); 5953 if (DebugFlags.WEB_VIEW) { 5954 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode 5955 + " count=" + count 5956 + " mTrackballRemainsX=" + mTrackballRemainsX 5957 + " mTrackballRemainsY=" + mTrackballRemainsY); 5958 } 5959 if (mNativeClass != 0 && nativeFocusIsPlugin()) { 5960 for (int i = 0; i < count; i++) { 5961 letPluginHandleNavKey(selectKeyCode, time, true); 5962 } 5963 letPluginHandleNavKey(selectKeyCode, time, false); 5964 } else if (navHandledKey(selectKeyCode, count, false, time)) { 5965 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); 5966 } 5967 mTrackballRemainsX = mTrackballRemainsY = 0; 5968 } 5969 if (count >= TRACKBALL_SCROLL_COUNT) { 5970 int xMove = scaleTrackballX(xRate, width); 5971 int yMove = scaleTrackballY(yRate, height); 5972 if (DebugFlags.WEB_VIEW) { 5973 Log.v(LOGTAG, "doTrackball pinScrollBy" 5974 + " count=" + count 5975 + " xMove=" + xMove + " yMove=" + yMove 5976 + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) 5977 + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) 5978 ); 5979 } 5980 if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { 5981 xMove = 0; 5982 } 5983 if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) { 5984 yMove = 0; 5985 } 5986 if (xMove != 0 || yMove != 0) { 5987 pinScrollBy(xMove, yMove, true, 0); 5988 } 5989 mUserScroll = true; 5990 } 5991 } 5992 5993 private int computeMaxScrollX() { 5994 return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0); 5995 } 5996 5997 private int computeMaxScrollY() { 5998 return Math.max(computeRealVerticalScrollRange() + getTitleHeight() 5999 - getViewHeightWithTitle(), 0); 6000 } 6001 6002 public void flingScroll(int vx, int vy) { 6003 mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0, 6004 computeMaxScrollY(), mOverflingDistance, mOverflingDistance); 6005 invalidate(); 6006 } 6007 6008 private void doFling() { 6009 if (mVelocityTracker == null) { 6010 return; 6011 } 6012 int maxX = computeMaxScrollX(); 6013 int maxY = computeMaxScrollY(); 6014 6015 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling); 6016 int vx = (int) mVelocityTracker.getXVelocity(); 6017 int vy = (int) mVelocityTracker.getYVelocity(); 6018 6019 if (mSnapScrollMode != SNAP_NONE) { 6020 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 6021 vy = 0; 6022 } else { 6023 vx = 0; 6024 } 6025 } 6026 if (true /* EMG release: make our fling more like Maps' */) { 6027 // maps cuts their velocity in half 6028 vx = vx * 3 / 4; 6029 vy = vy * 3 / 4; 6030 } 6031 if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { 6032 WebViewCore.resumePriority(); 6033 WebViewCore.resumeUpdatePicture(mWebViewCore); 6034 if (mScroller.springBack(mScrollX, mScrollY, 0, computeMaxScrollX(), 6035 0, computeMaxScrollY())) { 6036 invalidate(); 6037 } 6038 return; 6039 } 6040 float currentVelocity = mScroller.getCurrVelocity(); 6041 if (mLastVelocity > 0 && currentVelocity > 0) { 6042 float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX) 6043 - Math.atan2(vy, vx))); 6044 final float circle = (float) (Math.PI) * 2.0f; 6045 if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) { 6046 vx += currentVelocity * mLastVelX / mLastVelocity; 6047 vy += currentVelocity * mLastVelY / mLastVelocity; 6048 if (DebugFlags.WEB_VIEW) { 6049 Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy); 6050 } 6051 } else if (DebugFlags.WEB_VIEW) { 6052 Log.v(LOGTAG, "doFling missed " + deltaR / circle); 6053 } 6054 } else if (DebugFlags.WEB_VIEW) { 6055 Log.v(LOGTAG, "doFling start last=" + mLastVelocity 6056 + " current=" + currentVelocity 6057 + " vx=" + vx + " vy=" + vy 6058 + " maxX=" + maxX + " maxY=" + maxY 6059 + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY); 6060 } 6061 6062 // Allow sloppy flings without overscrolling at the edges. 6063 if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) { 6064 vx = 0; 6065 } 6066 if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) { 6067 vy = 0; 6068 } 6069 6070 if (mOverscrollDistance < mOverflingDistance) { 6071 if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) { 6072 vx = 0; 6073 } 6074 if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) { 6075 vy = 0; 6076 } 6077 } 6078 6079 mLastVelX = vx; 6080 mLastVelY = vy; 6081 mLastVelocity = (float) Math.hypot(vx, vy); 6082 6083 // no horizontal overscroll if the content just fits 6084 mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY, 6085 maxX == 0 ? 0 : mOverflingDistance, mOverflingDistance); 6086 // Duration is calculated based on velocity. With range boundaries and overscroll 6087 // we may not know how long the final animation will take. (Hence the deprecation 6088 // warning on the call below.) It's not a big deal for scroll bars but if webcore 6089 // resumes during this effect we will take a performance hit. See computeScroll; 6090 // we resume webcore there when the animation is finished. 6091 final int time = mScroller.getDuration(); 6092 awakenScrollBars(time); 6093 invalidate(); 6094 } 6095 6096 private boolean zoomWithPreview(float scale, boolean updateTextWrapScale) { 6097 float oldScale = mActualScale; 6098 mInitialScrollX = mScrollX; 6099 mInitialScrollY = mScrollY; 6100 6101 // snap to DEFAULT_SCALE if it is close 6102 if (Math.abs(scale - mDefaultScale) < MINIMUM_SCALE_INCREMENT) { 6103 scale = mDefaultScale; 6104 } 6105 6106 setNewZoomScale(scale, updateTextWrapScale, false); 6107 6108 if (oldScale != mActualScale) { 6109 // use mZoomPickerScale to see zoom preview first 6110 mZoomStart = SystemClock.uptimeMillis(); 6111 mInvInitialZoomScale = 1.0f / oldScale; 6112 mInvFinalZoomScale = 1.0f / mActualScale; 6113 mZoomScale = mActualScale; 6114 WebViewCore.pauseUpdatePicture(mWebViewCore); 6115 invalidate(); 6116 return true; 6117 } else { 6118 return false; 6119 } 6120 } 6121 6122 /** 6123 * Returns a view containing zoom controls i.e. +/- buttons. The caller is 6124 * in charge of installing this view to the view hierarchy. This view will 6125 * become visible when the user starts scrolling via touch and fade away if 6126 * the user does not interact with it. 6127 * <p/> 6128 * API version 3 introduces a built-in zoom mechanism that is shown 6129 * automatically by the MapView. This is the preferred approach for 6130 * showing the zoom UI. 6131 * 6132 * @deprecated The built-in zoom mechanism is preferred, see 6133 * {@link WebSettings#setBuiltInZoomControls(boolean)}. 6134 */ 6135 @Deprecated 6136 public View getZoomControls() { 6137 if (!getSettings().supportZoom()) { 6138 Log.w(LOGTAG, "This WebView doesn't support zoom."); 6139 return null; 6140 } 6141 if (mZoomControls == null) { 6142 mZoomControls = createZoomControls(); 6143 6144 /* 6145 * need to be set to VISIBLE first so that getMeasuredHeight() in 6146 * {@link #onSizeChanged()} can return the measured value for proper 6147 * layout. 6148 */ 6149 mZoomControls.setVisibility(View.VISIBLE); 6150 mZoomControlRunnable = new Runnable() { 6151 public void run() { 6152 6153 /* Don't dismiss the controls if the user has 6154 * focus on them. Wait and check again later. 6155 */ 6156 if (!mZoomControls.hasFocus()) { 6157 mZoomControls.hide(); 6158 } else { 6159 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6160 mPrivateHandler.postDelayed(mZoomControlRunnable, 6161 ZOOM_CONTROLS_TIMEOUT); 6162 } 6163 } 6164 }; 6165 } 6166 return mZoomControls; 6167 } 6168 6169 private ExtendedZoomControls createZoomControls() { 6170 ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext 6171 , null); 6172 zoomControls.setOnZoomInClickListener(new OnClickListener() { 6173 public void onClick(View v) { 6174 // reset time out 6175 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6176 mPrivateHandler.postDelayed(mZoomControlRunnable, 6177 ZOOM_CONTROLS_TIMEOUT); 6178 zoomIn(); 6179 } 6180 }); 6181 zoomControls.setOnZoomOutClickListener(new OnClickListener() { 6182 public void onClick(View v) { 6183 // reset time out 6184 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6185 mPrivateHandler.postDelayed(mZoomControlRunnable, 6186 ZOOM_CONTROLS_TIMEOUT); 6187 zoomOut(); 6188 } 6189 }); 6190 return zoomControls; 6191 } 6192 6193 /** 6194 * Gets the {@link ZoomButtonsController} which can be used to add 6195 * additional buttons to the zoom controls window. 6196 * 6197 * @return The instance of {@link ZoomButtonsController} used by this class, 6198 * or null if it is unavailable. 6199 * @hide 6200 */ 6201 public ZoomButtonsController getZoomButtonsController() { 6202 if (mZoomButtonsController == null) { 6203 mZoomButtonsController = new ZoomButtonsController(this); 6204 mZoomButtonsController.setOnZoomListener(mZoomListener); 6205 // ZoomButtonsController positions the buttons at the bottom, but in 6206 // the middle. Change their layout parameters so they appear on the 6207 // right. 6208 View controls = mZoomButtonsController.getZoomControls(); 6209 ViewGroup.LayoutParams params = controls.getLayoutParams(); 6210 if (params instanceof FrameLayout.LayoutParams) { 6211 FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams) params; 6212 frameParams.gravity = Gravity.RIGHT; 6213 } 6214 } 6215 return mZoomButtonsController; 6216 } 6217 6218 /** 6219 * Perform zoom in in the webview 6220 * @return TRUE if zoom in succeeds. FALSE if no zoom changes. 6221 */ 6222 public boolean zoomIn() { 6223 // TODO: alternatively we can disallow this during draw history mode 6224 switchOutDrawHistory(); 6225 mInZoomOverview = false; 6226 // Center zooming to the center of the screen. 6227 mZoomCenterX = getViewWidth() * .5f; 6228 mZoomCenterY = getViewHeight() * .5f; 6229 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 6230 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 6231 return zoomWithPreview(mActualScale * 1.25f, true); 6232 } 6233 6234 /** 6235 * Perform zoom out in the webview 6236 * @return TRUE if zoom out succeeds. FALSE if no zoom changes. 6237 */ 6238 public boolean zoomOut() { 6239 // TODO: alternatively we can disallow this during draw history mode 6240 switchOutDrawHistory(); 6241 // Center zooming to the center of the screen. 6242 mZoomCenterX = getViewWidth() * .5f; 6243 mZoomCenterY = getViewHeight() * .5f; 6244 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 6245 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 6246 return zoomWithPreview(mActualScale * 0.8f, true); 6247 } 6248 6249 private void updateSelection() { 6250 if (mNativeClass == 0) { 6251 return; 6252 } 6253 // mLastTouchX and mLastTouchY are the point in the current viewport 6254 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 6255 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 6256 Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop, 6257 contentX + mNavSlop, contentY + mNavSlop); 6258 nativeSelectBestAt(rect); 6259 } 6260 6261 /** 6262 * Scroll the focused text field/area to match the WebTextView 6263 * @param xPercent New x position of the WebTextView from 0 to 1. 6264 * @param y New y position of the WebTextView in view coordinates 6265 */ 6266 /*package*/ void scrollFocusedTextInput(float xPercent, int y) { 6267 if (!inEditingMode() || mWebViewCore == null) { 6268 return; 6269 } 6270 mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 6271 // Since this position is relative to the top of the text input 6272 // field, we do not need to take the title bar's height into 6273 // consideration. 6274 viewToContentDimension(y), 6275 new Float(xPercent)); 6276 } 6277 6278 /** 6279 * Set our starting point and time for a drag from the WebTextView. 6280 */ 6281 /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) { 6282 if (!inEditingMode()) { 6283 return; 6284 } 6285 mLastTouchX = x + (float) (mWebTextView.getLeft() - mScrollX); 6286 mLastTouchY = y + (float) (mWebTextView.getTop() - mScrollY); 6287 mLastTouchTime = eventTime; 6288 if (!mScroller.isFinished()) { 6289 abortAnimation(); 6290 mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); 6291 } 6292 mSnapScrollMode = SNAP_NONE; 6293 mVelocityTracker = VelocityTracker.obtain(); 6294 mTouchMode = TOUCH_DRAG_START_MODE; 6295 } 6296 6297 /** 6298 * Given a motion event from the WebTextView, set its location to our 6299 * coordinates, and handle the event. 6300 */ 6301 /*package*/ boolean textFieldDrag(MotionEvent event) { 6302 if (!inEditingMode()) { 6303 return false; 6304 } 6305 mDragFromTextInput = true; 6306 event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX), 6307 (float) (mWebTextView.getTop() - mScrollY)); 6308 boolean result = onTouchEvent(event); 6309 mDragFromTextInput = false; 6310 return result; 6311 } 6312 6313 /** 6314 * Due a touch up from a WebTextView. This will be handled by webkit to 6315 * change the selection. 6316 * @param event MotionEvent in the WebTextView's coordinates. 6317 */ 6318 /*package*/ void touchUpOnTextField(MotionEvent event) { 6319 if (!inEditingMode()) { 6320 return; 6321 } 6322 int x = viewToContentX((int) event.getX() + mWebTextView.getLeft()); 6323 int y = viewToContentY((int) event.getY() + mWebTextView.getTop()); 6324 nativeMotionUp(x, y, mNavSlop); 6325 } 6326 6327 /** 6328 * Called when pressing the center key or trackball on a textfield. 6329 */ 6330 /*package*/ void centerKeyPressOnTextField() { 6331 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 6332 nativeCursorNodePointer()); 6333 } 6334 6335 private void doShortPress() { 6336 if (mNativeClass == 0) { 6337 return; 6338 } 6339 if (mPreventDefault == PREVENT_DEFAULT_YES) { 6340 return; 6341 } 6342 mTouchMode = TOUCH_DONE_MODE; 6343 switchOutDrawHistory(); 6344 // mLastTouchX and mLastTouchY are the point in the current viewport 6345 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 6346 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 6347 if (nativePointInNavCache(contentX, contentY, mNavSlop)) { 6348 WebViewCore.MotionUpData motionUpData = new WebViewCore 6349 .MotionUpData(); 6350 motionUpData.mFrame = nativeCacheHitFramePointer(); 6351 motionUpData.mNode = nativeCacheHitNodePointer(); 6352 motionUpData.mBounds = nativeCacheHitNodeBounds(); 6353 motionUpData.mX = contentX; 6354 motionUpData.mY = contentY; 6355 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS, 6356 motionUpData); 6357 } else { 6358 doMotionUp(contentX, contentY); 6359 } 6360 } 6361 6362 private void doMotionUp(int contentX, int contentY) { 6363 if (mLogEvent && nativeMotionUp(contentX, contentY, mNavSlop)) { 6364 EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER); 6365 } 6366 if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { 6367 playSoundEffect(SoundEffectConstants.CLICK); 6368 } 6369 } 6370 6371 /* 6372 * Return true if the view (Plugin) is fully visible and maximized inside 6373 * the WebView. 6374 */ 6375 private boolean isPluginFitOnScreen(ViewManager.ChildView view) { 6376 int viewWidth = getViewWidth(); 6377 int viewHeight = getViewHeightWithTitle(); 6378 float scale = Math.min((float) viewWidth / view.width, 6379 (float) viewHeight / view.height); 6380 if (scale < mMinZoomScale) { 6381 scale = mMinZoomScale; 6382 } else if (scale > mMaxZoomScale) { 6383 scale = mMaxZoomScale; 6384 } 6385 if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) { 6386 if (contentToViewX(view.x) >= mScrollX 6387 && contentToViewX(view.x + view.width) <= mScrollX 6388 + viewWidth 6389 && contentToViewY(view.y) >= mScrollY 6390 && contentToViewY(view.y + view.height) <= mScrollY 6391 + viewHeight) { 6392 return true; 6393 } 6394 } 6395 return false; 6396 } 6397 6398 /* 6399 * Maximize and center the rectangle, specified in the document coordinate 6400 * space, inside the WebView. If the zoom doesn't need to be changed, do an 6401 * animated scroll to center it. If the zoom needs to be changed, find the 6402 * zoom center and do a smooth zoom transition. 6403 */ 6404 private void centerFitRect(int docX, int docY, int docWidth, int docHeight) { 6405 int viewWidth = getViewWidth(); 6406 int viewHeight = getViewHeightWithTitle(); 6407 float scale = Math.min((float) viewWidth / docWidth, (float) viewHeight 6408 / docHeight); 6409 if (scale < mMinZoomScale) { 6410 scale = mMinZoomScale; 6411 } else if (scale > mMaxZoomScale) { 6412 scale = mMaxZoomScale; 6413 } 6414 if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) { 6415 pinScrollTo(contentToViewX(docX + docWidth / 2) - viewWidth / 2, 6416 contentToViewY(docY + docHeight / 2) - viewHeight / 2, 6417 true, 0); 6418 } else { 6419 float oldScreenX = docX * mActualScale - mScrollX; 6420 float rectViewX = docX * scale; 6421 float rectViewWidth = docWidth * scale; 6422 float newMaxWidth = mContentWidth * scale; 6423 float newScreenX = (viewWidth - rectViewWidth) / 2; 6424 // pin the newX to the WebView 6425 if (newScreenX > rectViewX) { 6426 newScreenX = rectViewX; 6427 } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) { 6428 newScreenX = viewWidth - (newMaxWidth - rectViewX); 6429 } 6430 mZoomCenterX = (oldScreenX * scale - newScreenX * mActualScale) 6431 / (scale - mActualScale); 6432 float oldScreenY = docY * mActualScale + getTitleHeight() 6433 - mScrollY; 6434 float rectViewY = docY * scale + getTitleHeight(); 6435 float rectViewHeight = docHeight * scale; 6436 float newMaxHeight = mContentHeight * scale + getTitleHeight(); 6437 float newScreenY = (viewHeight - rectViewHeight) / 2; 6438 // pin the newY to the WebView 6439 if (newScreenY > rectViewY) { 6440 newScreenY = rectViewY; 6441 } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) { 6442 newScreenY = viewHeight - (newMaxHeight - rectViewY); 6443 } 6444 mZoomCenterY = (oldScreenY * scale - newScreenY * mActualScale) 6445 / (scale - mActualScale); 6446 zoomWithPreview(scale, false); 6447 } 6448 } 6449 6450 void dismissZoomControl() { 6451 if (mWebViewCore == null) { 6452 // maybe called after WebView's destroy(). As we can't get settings, 6453 // just hide zoom control for both styles. 6454 if (mZoomButtonsController != null) { 6455 mZoomButtonsController.setVisible(false); 6456 } 6457 if (mZoomControls != null) { 6458 mZoomControls.hide(); 6459 } 6460 return; 6461 } 6462 WebSettings settings = getSettings(); 6463 if (settings.getBuiltInZoomControls()) { 6464 if (mZoomButtonsController != null) { 6465 mZoomButtonsController.setVisible(false); 6466 } 6467 } else { 6468 if (mZoomControlRunnable != null) { 6469 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6470 } 6471 if (mZoomControls != null) { 6472 mZoomControls.hide(); 6473 } 6474 } 6475 } 6476 6477 // Rule for double tap: 6478 // 1. if the current scale is not same as the text wrap scale and layout 6479 // algorithm is NARROW_COLUMNS, fit to column; 6480 // 2. if the current state is not overview mode, change to overview mode; 6481 // 3. if the current state is overview mode, change to default scale. 6482 private void doDoubleTap() { 6483 if (mWebViewCore.getSettings().getUseWideViewPort() == false) { 6484 return; 6485 } 6486 mZoomCenterX = mLastTouchX; 6487 mZoomCenterY = mLastTouchY; 6488 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 6489 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 6490 WebSettings settings = getSettings(); 6491 settings.setDoubleTapToastCount(0); 6492 // remove the zoom control after double tap 6493 dismissZoomControl(); 6494 ViewManager.ChildView plugin = mViewManager.hitTest(mAnchorX, mAnchorY); 6495 if (plugin != null) { 6496 if (isPluginFitOnScreen(plugin)) { 6497 mInZoomOverview = true; 6498 // Force the titlebar fully reveal in overview mode 6499 if (mScrollY < getTitleHeight()) mScrollY = 0; 6500 zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth, 6501 true); 6502 } else { 6503 mInZoomOverview = false; 6504 centerFitRect(plugin.x, plugin.y, plugin.width, plugin.height); 6505 } 6506 return; 6507 } 6508 boolean zoomToDefault = false; 6509 if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS) 6510 && (Math.abs(mActualScale - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT)) { 6511 setNewZoomScale(mActualScale, true, true); 6512 float overviewScale = (float) getViewWidth() / mZoomOverviewWidth; 6513 if (Math.abs(mActualScale - overviewScale) < MINIMUM_SCALE_INCREMENT) { 6514 mInZoomOverview = true; 6515 } 6516 } else if (!mInZoomOverview) { 6517 float newScale = (float) getViewWidth() / mZoomOverviewWidth; 6518 if (Math.abs(mActualScale - newScale) >= MINIMUM_SCALE_INCREMENT) { 6519 mInZoomOverview = true; 6520 // Force the titlebar fully reveal in overview mode 6521 if (mScrollY < getTitleHeight()) mScrollY = 0; 6522 zoomWithPreview(newScale, true); 6523 } else if (Math.abs(mActualScale - mDefaultScale) >= MINIMUM_SCALE_INCREMENT) { 6524 zoomToDefault = true; 6525 } 6526 } else { 6527 zoomToDefault = true; 6528 } 6529 if (zoomToDefault) { 6530 mInZoomOverview = false; 6531 int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale); 6532 if (left != NO_LEFTEDGE) { 6533 // add a 5pt padding to the left edge. 6534 int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5)) 6535 - mScrollX; 6536 // Re-calculate the zoom center so that the new scroll x will be 6537 // on the left edge. 6538 if (viewLeft > 0) { 6539 mZoomCenterX = viewLeft * mDefaultScale 6540 / (mDefaultScale - mActualScale); 6541 } else { 6542 scrollBy(viewLeft, 0); 6543 mZoomCenterX = 0; 6544 } 6545 } 6546 zoomWithPreview(mDefaultScale, true); 6547 } 6548 } 6549 6550 // Called by JNI to handle a touch on a node representing an email address, 6551 // address, or phone number 6552 private void overrideLoading(String url) { 6553 mCallbackProxy.uiOverrideUrlLoading(url); 6554 } 6555 6556 @Override 6557 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 6558 boolean result = false; 6559 if (inEditingMode()) { 6560 result = mWebTextView.requestFocus(direction, 6561 previouslyFocusedRect); 6562 } else { 6563 result = super.requestFocus(direction, previouslyFocusedRect); 6564 if (mWebViewCore.getSettings().getNeedInitialFocus()) { 6565 // For cases such as GMail, where we gain focus from a direction, 6566 // we want to move to the first available link. 6567 // FIXME: If there are no visible links, we may not want to 6568 int fakeKeyDirection = 0; 6569 switch(direction) { 6570 case View.FOCUS_UP: 6571 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP; 6572 break; 6573 case View.FOCUS_DOWN: 6574 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN; 6575 break; 6576 case View.FOCUS_LEFT: 6577 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT; 6578 break; 6579 case View.FOCUS_RIGHT: 6580 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT; 6581 break; 6582 default: 6583 return result; 6584 } 6585 if (mNativeClass != 0 && !nativeHasCursorNode()) { 6586 navHandledKey(fakeKeyDirection, 1, true, 0); 6587 } 6588 } 6589 } 6590 return result; 6591 } 6592 6593 @Override 6594 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 6595 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 6596 6597 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 6598 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 6599 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 6600 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 6601 6602 int measuredHeight = heightSize; 6603 int measuredWidth = widthSize; 6604 6605 // Grab the content size from WebViewCore. 6606 int contentHeight = contentToViewDimension(mContentHeight); 6607 int contentWidth = contentToViewDimension(mContentWidth); 6608 6609 // Log.d(LOGTAG, "------- measure " + heightMode); 6610 6611 if (heightMode != MeasureSpec.EXACTLY) { 6612 mHeightCanMeasure = true; 6613 measuredHeight = contentHeight; 6614 if (heightMode == MeasureSpec.AT_MOST) { 6615 // If we are larger than the AT_MOST height, then our height can 6616 // no longer be measured and we should scroll internally. 6617 if (measuredHeight > heightSize) { 6618 measuredHeight = heightSize; 6619 mHeightCanMeasure = false; 6620 } 6621 } 6622 } else { 6623 mHeightCanMeasure = false; 6624 } 6625 if (mNativeClass != 0) { 6626 nativeSetHeightCanMeasure(mHeightCanMeasure); 6627 } 6628 // For the width, always use the given size unless unspecified. 6629 if (widthMode == MeasureSpec.UNSPECIFIED) { 6630 mWidthCanMeasure = true; 6631 measuredWidth = contentWidth; 6632 } else { 6633 mWidthCanMeasure = false; 6634 } 6635 6636 synchronized (this) { 6637 setMeasuredDimension(measuredWidth, measuredHeight); 6638 } 6639 } 6640 6641 @Override 6642 public boolean requestChildRectangleOnScreen(View child, 6643 Rect rect, 6644 boolean immediate) { 6645 rect.offset(child.getLeft() - child.getScrollX(), 6646 child.getTop() - child.getScrollY()); 6647 6648 Rect content = new Rect(viewToContentX(mScrollX), 6649 viewToContentY(mScrollY), 6650 viewToContentX(mScrollX + getWidth() 6651 - getVerticalScrollbarWidth()), 6652 viewToContentY(mScrollY + getViewHeightWithTitle())); 6653 content = nativeSubtractLayers(content); 6654 int screenTop = contentToViewY(content.top); 6655 int screenBottom = contentToViewY(content.bottom); 6656 int height = screenBottom - screenTop; 6657 int scrollYDelta = 0; 6658 6659 if (rect.bottom > screenBottom) { 6660 int oneThirdOfScreenHeight = height / 3; 6661 if (rect.height() > 2 * oneThirdOfScreenHeight) { 6662 // If the rectangle is too tall to fit in the bottom two thirds 6663 // of the screen, place it at the top. 6664 scrollYDelta = rect.top - screenTop; 6665 } else { 6666 // If the rectangle will still fit on screen, we want its 6667 // top to be in the top third of the screen. 6668 scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight); 6669 } 6670 } else if (rect.top < screenTop) { 6671 scrollYDelta = rect.top - screenTop; 6672 } 6673 6674 int screenLeft = contentToViewX(content.left); 6675 int screenRight = contentToViewX(content.right); 6676 int width = screenRight - screenLeft; 6677 int scrollXDelta = 0; 6678 6679 if (rect.right > screenRight && rect.left > screenLeft) { 6680 if (rect.width() > width) { 6681 scrollXDelta += (rect.left - screenLeft); 6682 } else { 6683 scrollXDelta += (rect.right - screenRight); 6684 } 6685 } else if (rect.left < screenLeft) { 6686 scrollXDelta -= (screenLeft - rect.left); 6687 } 6688 6689 if ((scrollYDelta | scrollXDelta) != 0) { 6690 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0); 6691 } 6692 6693 return false; 6694 } 6695 6696 /* package */ void replaceTextfieldText(int oldStart, int oldEnd, 6697 String replace, int newStart, int newEnd) { 6698 WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData(); 6699 arg.mReplace = replace; 6700 arg.mNewStart = newStart; 6701 arg.mNewEnd = newEnd; 6702 mTextGeneration++; 6703 arg.mTextGeneration = mTextGeneration; 6704 mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); 6705 } 6706 6707 /* package */ void passToJavaScript(String currentText, KeyEvent event) { 6708 WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData(); 6709 arg.mEvent = event; 6710 arg.mCurrentText = currentText; 6711 // Increase our text generation number, and pass it to webcore thread 6712 mTextGeneration++; 6713 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); 6714 // WebKit's document state is not saved until about to leave the page. 6715 // To make sure the host application, like Browser, has the up to date 6716 // document state when it goes to background, we force to save the 6717 // document state. 6718 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); 6719 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, 6720 cursorData(), 1000); 6721 } 6722 6723 /* package */ synchronized WebViewCore getWebViewCore() { 6724 return mWebViewCore; 6725 } 6726 6727 //------------------------------------------------------------------------- 6728 // Methods can be called from a separate thread, like WebViewCore 6729 // If it needs to call the View system, it has to send message. 6730 //------------------------------------------------------------------------- 6731 6732 /** 6733 * General handler to receive message coming from webkit thread 6734 */ 6735 class PrivateHandler extends Handler { 6736 @Override 6737 public void handleMessage(Message msg) { 6738 // exclude INVAL_RECT_MSG_ID since it is frequently output 6739 if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) { 6740 if (msg.what >= FIRST_PRIVATE_MSG_ID 6741 && msg.what <= LAST_PRIVATE_MSG_ID) { 6742 Log.v(LOGTAG, HandlerPrivateDebugString[msg.what 6743 - FIRST_PRIVATE_MSG_ID]); 6744 } else if (msg.what >= FIRST_PACKAGE_MSG_ID 6745 && msg.what <= LAST_PACKAGE_MSG_ID) { 6746 Log.v(LOGTAG, HandlerPackageDebugString[msg.what 6747 - FIRST_PACKAGE_MSG_ID]); 6748 } else { 6749 Log.v(LOGTAG, Integer.toString(msg.what)); 6750 } 6751 } 6752 if (mWebViewCore == null) { 6753 // after WebView's destroy() is called, skip handling messages. 6754 return; 6755 } 6756 switch (msg.what) { 6757 case REMEMBER_PASSWORD: { 6758 mDatabase.setUsernamePassword( 6759 msg.getData().getString("host"), 6760 msg.getData().getString("username"), 6761 msg.getData().getString("password")); 6762 ((Message) msg.obj).sendToTarget(); 6763 break; 6764 } 6765 case NEVER_REMEMBER_PASSWORD: { 6766 mDatabase.setUsernamePassword( 6767 msg.getData().getString("host"), null, null); 6768 ((Message) msg.obj).sendToTarget(); 6769 break; 6770 } 6771 case PREVENT_DEFAULT_TIMEOUT: { 6772 // if timeout happens, cancel it so that it won't block UI 6773 // to continue handling touch events 6774 if ((msg.arg1 == MotionEvent.ACTION_DOWN 6775 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) 6776 || (msg.arg1 == MotionEvent.ACTION_MOVE 6777 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) { 6778 cancelWebCoreTouchEvent( 6779 viewToContentX((int) mLastTouchX + mScrollX), 6780 viewToContentY((int) mLastTouchY + mScrollY), 6781 true); 6782 } 6783 break; 6784 } 6785 case SWITCH_TO_SHORTPRESS: { 6786 if (mTouchMode == TOUCH_INIT_MODE) { 6787 if (mPreventDefault != PREVENT_DEFAULT_YES) { 6788 mTouchMode = TOUCH_SHORTPRESS_START_MODE; 6789 updateSelection(); 6790 } else { 6791 // set to TOUCH_SHORTPRESS_MODE so that it won't 6792 // trigger double tap any more 6793 mTouchMode = TOUCH_SHORTPRESS_MODE; 6794 } 6795 } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 6796 mTouchMode = TOUCH_DONE_MODE; 6797 } 6798 break; 6799 } 6800 case SWITCH_TO_LONGPRESS: { 6801 if (inFullScreenMode() || mDeferTouchProcess) { 6802 TouchEventData ted = new TouchEventData(); 6803 ted.mAction = WebViewCore.ACTION_LONGPRESS; 6804 ted.mX = viewToContentX((int) mLastTouchX + mScrollX); 6805 ted.mY = viewToContentY((int) mLastTouchY + mScrollY); 6806 // metaState for long press is tricky. Should it be the 6807 // state when the press started or when the press was 6808 // released? Or some intermediary key state? For 6809 // simplicity for now, we don't set it. 6810 ted.mMetaState = 0; 6811 ted.mReprocess = mDeferTouchProcess; 6812 if (mDeferTouchProcess) { 6813 ted.mViewX = mLastTouchX; 6814 ted.mViewY = mLastTouchY; 6815 } 6816 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 6817 } else if (mPreventDefault != PREVENT_DEFAULT_YES) { 6818 mTouchMode = TOUCH_DONE_MODE; 6819 performLongClick(); 6820 rebuildWebTextView(); 6821 } 6822 break; 6823 } 6824 case RELEASE_SINGLE_TAP: { 6825 doShortPress(); 6826 break; 6827 } 6828 case SCROLL_BY_MSG_ID: 6829 setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj); 6830 break; 6831 case SYNC_SCROLL_TO_MSG_ID: 6832 if (mUserScroll) { 6833 // if user has scrolled explicitly, don't sync the 6834 // scroll position any more 6835 mUserScroll = false; 6836 break; 6837 } 6838 // fall through 6839 case SCROLL_TO_MSG_ID: 6840 if (setContentScrollTo(msg.arg1, msg.arg2)) { 6841 // if we can't scroll to the exact position due to pin, 6842 // send a message to WebCore to re-scroll when we get a 6843 // new picture 6844 mUserScroll = false; 6845 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, 6846 msg.arg1, msg.arg2); 6847 } 6848 break; 6849 case SPAWN_SCROLL_TO_MSG_ID: 6850 spawnContentScrollTo(msg.arg1, msg.arg2); 6851 break; 6852 case UPDATE_ZOOM_RANGE: { 6853 WebViewCore.RestoreState restoreState 6854 = (WebViewCore.RestoreState) msg.obj; 6855 // mScrollX contains the new minPrefWidth 6856 updateZoomRange(restoreState, getViewWidth(), 6857 restoreState.mScrollX, false); 6858 break; 6859 } 6860 case NEW_PICTURE_MSG_ID: { 6861 // If we've previously delayed deleting a root 6862 // layer, do it now. 6863 if (mDelayedDeleteRootLayer) { 6864 mDelayedDeleteRootLayer = false; 6865 nativeSetRootLayer(0); 6866 } 6867 WebSettings settings = mWebViewCore.getSettings(); 6868 // called for new content 6869 final int viewWidth = getViewWidth(); 6870 final WebViewCore.DrawData draw = 6871 (WebViewCore.DrawData) msg.obj; 6872 final Point viewSize = draw.mViewPoint; 6873 boolean useWideViewport = settings.getUseWideViewPort(); 6874 WebViewCore.RestoreState restoreState = draw.mRestoreState; 6875 boolean hasRestoreState = restoreState != null; 6876 if (hasRestoreState) { 6877 updateZoomRange(restoreState, viewSize.x, 6878 draw.mMinPrefWidth, true); 6879 if (!mDrawHistory) { 6880 mInZoomOverview = false; 6881 6882 if (mInitialScaleInPercent > 0) { 6883 setNewZoomScale(mInitialScaleInPercent / 100.0f, 6884 mInitialScaleInPercent != mTextWrapScale * 100, 6885 false); 6886 } else if (restoreState.mViewScale > 0) { 6887 mTextWrapScale = restoreState.mTextWrapScale; 6888 setNewZoomScale(restoreState.mViewScale, false, 6889 false); 6890 } else { 6891 mInZoomOverview = useWideViewport 6892 && settings.getLoadWithOverviewMode(); 6893 float scale; 6894 if (mInZoomOverview) { 6895 scale = (float) viewWidth 6896 / DEFAULT_VIEWPORT_WIDTH; 6897 } else { 6898 scale = restoreState.mTextWrapScale; 6899 } 6900 setNewZoomScale(scale, Math.abs(scale 6901 - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT, 6902 false); 6903 } 6904 setContentScrollTo(restoreState.mScrollX, 6905 restoreState.mScrollY); 6906 // As we are on a new page, remove the WebTextView. This 6907 // is necessary for page loads driven by webkit, and in 6908 // particular when the user was on a password field, so 6909 // the WebTextView was visible. 6910 clearTextEntry(false); 6911 // update the zoom buttons as the scale can be changed 6912 if (getSettings().getBuiltInZoomControls()) { 6913 updateZoomButtonsEnabled(); 6914 } 6915 } 6916 } 6917 // We update the layout (i.e. request a layout from the 6918 // view system) if the last view size that we sent to 6919 // WebCore matches the view size of the picture we just 6920 // received in the fixed dimension. 6921 final boolean updateLayout = viewSize.x == mLastWidthSent 6922 && viewSize.y == mLastHeightSent; 6923 recordNewContentSize(draw.mWidthHeight.x, 6924 draw.mWidthHeight.y 6925 + (mFindIsUp ? mFindHeight : 0), updateLayout); 6926 if (DebugFlags.WEB_VIEW) { 6927 Rect b = draw.mInvalRegion.getBounds(); 6928 Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + 6929 b.left+","+b.top+","+b.right+","+b.bottom+"}"); 6930 } 6931 invalidateContentRect(draw.mInvalRegion.getBounds()); 6932 if (mPictureListener != null) { 6933 mPictureListener.onNewPicture(WebView.this, capturePicture()); 6934 } 6935 if (useWideViewport) { 6936 // limit mZoomOverviewWidth upper bound to 6937 // sMaxViewportWidth so that if the page doesn't behave 6938 // well, the WebView won't go insane. limit the lower 6939 // bound to match the default scale for mobile sites. 6940 mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math 6941 .max((int) (viewWidth / mDefaultScale), Math 6942 .max(draw.mMinPrefWidth, 6943 draw.mViewPoint.x))); 6944 } 6945 if (!mMinZoomScaleFixed) { 6946 mMinZoomScale = (float) viewWidth / mZoomOverviewWidth; 6947 } 6948 if (!mDrawHistory && mInZoomOverview) { 6949 // fit the content width to the current view. Ignore 6950 // the rounding error case. 6951 if (Math.abs((viewWidth * mInvActualScale) 6952 - mZoomOverviewWidth) > 1) { 6953 setNewZoomScale((float) viewWidth 6954 / mZoomOverviewWidth, Math.abs(mActualScale 6955 - mTextWrapScale) < MINIMUM_SCALE_INCREMENT, 6956 false); 6957 } 6958 } 6959 if (draw.mFocusSizeChanged && inEditingMode()) { 6960 mFocusSizeChanged = true; 6961 } 6962 if (hasRestoreState) { 6963 mViewManager.postReadyToDrawAll(); 6964 } 6965 break; 6966 } 6967 case WEBCORE_INITIALIZED_MSG_ID: 6968 // nativeCreate sets mNativeClass to a non-zero value 6969 nativeCreate(msg.arg1); 6970 break; 6971 case UPDATE_TEXTFIELD_TEXT_MSG_ID: 6972 // Make sure that the textfield is currently focused 6973 // and representing the same node as the pointer. 6974 if (inEditingMode() && 6975 mWebTextView.isSameTextField(msg.arg1)) { 6976 if (msg.getData().getBoolean("password")) { 6977 Spannable text = (Spannable) mWebTextView.getText(); 6978 int start = Selection.getSelectionStart(text); 6979 int end = Selection.getSelectionEnd(text); 6980 mWebTextView.setInPassword(true); 6981 // Restore the selection, which may have been 6982 // ruined by setInPassword. 6983 Spannable pword = 6984 (Spannable) mWebTextView.getText(); 6985 Selection.setSelection(pword, start, end); 6986 // If the text entry has created more events, ignore 6987 // this one. 6988 } else if (msg.arg2 == mTextGeneration) { 6989 mWebTextView.setTextAndKeepSelection( 6990 (String) msg.obj); 6991 } 6992 } 6993 break; 6994 case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID: 6995 displaySoftKeyboard(true); 6996 updateTextSelectionFromMessage(msg.arg1, msg.arg2, 6997 (WebViewCore.TextSelectionData) msg.obj); 6998 break; 6999 case UPDATE_TEXT_SELECTION_MSG_ID: 7000 // If no textfield was in focus, and the user touched one, 7001 // causing it to send this message, then WebTextView has not 7002 // been set up yet. Rebuild it so it can set its selection. 7003 rebuildWebTextView(); 7004 updateTextSelectionFromMessage(msg.arg1, msg.arg2, 7005 (WebViewCore.TextSelectionData) msg.obj); 7006 break; 7007 case RETURN_LABEL: 7008 if (inEditingMode() 7009 && mWebTextView.isSameTextField(msg.arg1)) { 7010 mWebTextView.setHint((String) msg.obj); 7011 InputMethodManager imm 7012 = InputMethodManager.peekInstance(); 7013 // The hint is propagated to the IME in 7014 // onCreateInputConnection. If the IME is already 7015 // active, restart it so that its hint text is updated. 7016 if (imm != null && imm.isActive(mWebTextView)) { 7017 imm.restartInput(mWebTextView); 7018 } 7019 } 7020 break; 7021 case MOVE_OUT_OF_PLUGIN: 7022 navHandledKey(msg.arg1, 1, false, 0); 7023 break; 7024 case UPDATE_TEXT_ENTRY_MSG_ID: 7025 // this is sent after finishing resize in WebViewCore. Make 7026 // sure the text edit box is still on the screen. 7027 selectionDone(); 7028 if (inEditingMode() && nativeCursorIsTextInput()) { 7029 mWebTextView.bringIntoView(); 7030 rebuildWebTextView(); 7031 } 7032 break; 7033 case CLEAR_TEXT_ENTRY: 7034 clearTextEntry(false); 7035 break; 7036 case INVAL_RECT_MSG_ID: { 7037 Rect r = (Rect)msg.obj; 7038 if (r == null) { 7039 invalidate(); 7040 } else { 7041 // we need to scale r from content into view coords, 7042 // which viewInvalidate() does for us 7043 viewInvalidate(r.left, r.top, r.right, r.bottom); 7044 } 7045 break; 7046 } 7047 case IMMEDIATE_REPAINT_MSG_ID: { 7048 invalidate(); 7049 break; 7050 } 7051 case SET_ROOT_LAYER_MSG_ID: { 7052 if (0 == msg.arg1) { 7053 // Null indicates deleting the old layer, but 7054 // don't actually do so until we've got the 7055 // new page to display. 7056 mDelayedDeleteRootLayer = true; 7057 } else { 7058 mDelayedDeleteRootLayer = false; 7059 nativeSetRootLayer(msg.arg1); 7060 invalidate(); 7061 } 7062 break; 7063 } 7064 case REQUEST_FORM_DATA: 7065 AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj; 7066 if (mWebTextView.isSameTextField(msg.arg1)) { 7067 mWebTextView.setAdapterCustom(adapter); 7068 } 7069 break; 7070 case RESUME_WEBCORE_PRIORITY: 7071 WebViewCore.resumePriority(); 7072 WebViewCore.resumeUpdatePicture(mWebViewCore); 7073 break; 7074 7075 case LONG_PRESS_CENTER: 7076 // as this is shared by keydown and trackballdown, reset all 7077 // the states 7078 mGotCenterDown = false; 7079 mTrackballDown = false; 7080 performLongClick(); 7081 break; 7082 7083 case WEBCORE_NEED_TOUCH_EVENTS: 7084 mForwardTouchEvents = (msg.arg1 != 0); 7085 break; 7086 7087 case PREVENT_TOUCH_ID: 7088 if (inFullScreenMode()) { 7089 break; 7090 } 7091 if (msg.obj == null) { 7092 if (msg.arg1 == MotionEvent.ACTION_DOWN 7093 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) { 7094 // if prevent default is called from WebCore, UI 7095 // will not handle the rest of the touch events any 7096 // more. 7097 mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES 7098 : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN; 7099 } else if (msg.arg1 == MotionEvent.ACTION_MOVE 7100 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 7101 // the return for the first ACTION_MOVE will decide 7102 // whether UI will handle touch or not. Currently no 7103 // support for alternating prevent default 7104 mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES 7105 : PREVENT_DEFAULT_NO; 7106 } 7107 } else if (msg.arg2 == 0) { 7108 // prevent default is not called in WebCore, so the 7109 // message needs to be reprocessed in UI 7110 TouchEventData ted = (TouchEventData) msg.obj; 7111 switch (ted.mAction) { 7112 case MotionEvent.ACTION_DOWN: 7113 mLastDeferTouchX = ted.mViewX; 7114 mLastDeferTouchY = ted.mViewY; 7115 mDeferTouchMode = TOUCH_INIT_MODE; 7116 break; 7117 case MotionEvent.ACTION_MOVE: { 7118 // no snapping in defer process 7119 if (mDeferTouchMode != TOUCH_DRAG_MODE) { 7120 mDeferTouchMode = TOUCH_DRAG_MODE; 7121 mLastDeferTouchX = ted.mViewX; 7122 mLastDeferTouchY = ted.mViewY; 7123 startDrag(); 7124 } 7125 int deltaX = pinLocX((int) (mScrollX 7126 + mLastDeferTouchX - ted.mViewX)) 7127 - mScrollX; 7128 int deltaY = pinLocY((int) (mScrollY 7129 + mLastDeferTouchY - ted.mViewY)) 7130 - mScrollY; 7131 doDrag(deltaX, deltaY); 7132 if (deltaX != 0) mLastDeferTouchX = ted.mViewX; 7133 if (deltaY != 0) mLastDeferTouchY = ted.mViewY; 7134 break; 7135 } 7136 case MotionEvent.ACTION_UP: 7137 case MotionEvent.ACTION_CANCEL: 7138 if (mDeferTouchMode == TOUCH_DRAG_MODE) { 7139 // no fling in defer process 7140 mScroller.springBack(mScrollX, mScrollY, 0, 7141 computeMaxScrollX(), 0, 7142 computeMaxScrollY()); 7143 invalidate(); 7144 WebViewCore.resumePriority(); 7145 WebViewCore.resumeUpdatePicture(mWebViewCore); 7146 } 7147 mDeferTouchMode = TOUCH_DONE_MODE; 7148 break; 7149 case WebViewCore.ACTION_DOUBLETAP: 7150 // doDoubleTap() needs mLastTouchX/Y as anchor 7151 mLastTouchX = ted.mViewX; 7152 mLastTouchY = ted.mViewY; 7153 doDoubleTap(); 7154 mDeferTouchMode = TOUCH_DONE_MODE; 7155 break; 7156 case WebViewCore.ACTION_LONGPRESS: 7157 HitTestResult hitTest = getHitTestResult(); 7158 if (hitTest != null && hitTest.mType 7159 != HitTestResult.UNKNOWN_TYPE) { 7160 performLongClick(); 7161 rebuildWebTextView(); 7162 } 7163 mDeferTouchMode = TOUCH_DONE_MODE; 7164 break; 7165 } 7166 } 7167 break; 7168 7169 case REQUEST_KEYBOARD: 7170 if (msg.arg1 == 0) { 7171 hideSoftKeyboard(); 7172 } else { 7173 displaySoftKeyboard(false); 7174 } 7175 break; 7176 7177 case FIND_AGAIN: 7178 // Ignore if find has been dismissed. 7179 if (mFindIsUp) { 7180 findAll(mLastFind); 7181 } 7182 break; 7183 7184 case DRAG_HELD_MOTIONLESS: 7185 mHeldMotionless = MOTIONLESS_TRUE; 7186 invalidate(); 7187 // fall through to keep scrollbars awake 7188 7189 case AWAKEN_SCROLL_BARS: 7190 if (mTouchMode == TOUCH_DRAG_MODE 7191 && mHeldMotionless == MOTIONLESS_TRUE) { 7192 awakenScrollBars(ViewConfiguration 7193 .getScrollDefaultDelay(), false); 7194 mPrivateHandler.sendMessageDelayed(mPrivateHandler 7195 .obtainMessage(AWAKEN_SCROLL_BARS), 7196 ViewConfiguration.getScrollDefaultDelay()); 7197 } 7198 break; 7199 7200 case DO_MOTION_UP: 7201 doMotionUp(msg.arg1, msg.arg2); 7202 break; 7203 7204 case SHOW_FULLSCREEN: { 7205 View view = (View) msg.obj; 7206 int npp = msg.arg1; 7207 7208 if (mFullScreenHolder != null) { 7209 Log.w(LOGTAG, "Should not have another full screen."); 7210 mFullScreenHolder.dismiss(); 7211 } 7212 mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp); 7213 mFullScreenHolder.setContentView(view); 7214 mFullScreenHolder.setCancelable(false); 7215 mFullScreenHolder.setCanceledOnTouchOutside(false); 7216 mFullScreenHolder.show(); 7217 7218 break; 7219 } 7220 case HIDE_FULLSCREEN: 7221 if (inFullScreenMode()) { 7222 mFullScreenHolder.dismiss(); 7223 mFullScreenHolder = null; 7224 } 7225 break; 7226 7227 case DOM_FOCUS_CHANGED: 7228 if (inEditingMode()) { 7229 nativeClearCursor(); 7230 rebuildWebTextView(); 7231 } 7232 break; 7233 7234 case SHOW_RECT_MSG_ID: { 7235 WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj; 7236 int x = mScrollX; 7237 int left = contentToViewX(data.mLeft); 7238 int width = contentToViewDimension(data.mWidth); 7239 int maxWidth = contentToViewDimension(data.mContentWidth); 7240 int viewWidth = getViewWidth(); 7241 if (width < viewWidth) { 7242 // center align 7243 x += left + width / 2 - mScrollX - viewWidth / 2; 7244 } else { 7245 x += (int) (left + data.mXPercentInDoc * width 7246 - mScrollX - data.mXPercentInView * viewWidth); 7247 } 7248 if (DebugFlags.WEB_VIEW) { 7249 Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" + 7250 width + ",maxWidth=" + maxWidth + 7251 ",viewWidth=" + viewWidth + ",x=" 7252 + x + ",xPercentInDoc=" + data.mXPercentInDoc + 7253 ",xPercentInView=" + data.mXPercentInView+ ")"); 7254 } 7255 // use the passing content width to cap x as the current 7256 // mContentWidth may not be updated yet 7257 x = Math.max(0, 7258 (Math.min(maxWidth, x + viewWidth)) - viewWidth); 7259 int top = contentToViewY(data.mTop); 7260 int height = contentToViewDimension(data.mHeight); 7261 int maxHeight = contentToViewDimension(data.mContentHeight); 7262 int viewHeight = getViewHeight(); 7263 int y = (int) (top + data.mYPercentInDoc * height - 7264 data.mYPercentInView * viewHeight); 7265 if (DebugFlags.WEB_VIEW) { 7266 Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" + 7267 height + ",maxHeight=" + maxHeight + 7268 ",viewHeight=" + viewHeight + ",y=" 7269 + y + ",yPercentInDoc=" + data.mYPercentInDoc + 7270 ",yPercentInView=" + data.mYPercentInView+ ")"); 7271 } 7272 // use the passing content height to cap y as the current 7273 // mContentHeight may not be updated yet 7274 y = Math.max(0, 7275 (Math.min(maxHeight, y + viewHeight) - viewHeight)); 7276 // We need to take into account the visible title height 7277 // when scrolling since y is an absolute view position. 7278 y = Math.max(0, y - getVisibleTitleHeight()); 7279 scrollTo(x, y); 7280 } 7281 break; 7282 7283 case CENTER_FIT_RECT: 7284 Rect r = (Rect)msg.obj; 7285 mInZoomOverview = false; 7286 centerFitRect(r.left, r.top, r.width(), r.height()); 7287 break; 7288 7289 case SET_SCROLLBAR_MODES: 7290 mHorizontalScrollBarMode = msg.arg1; 7291 mVerticalScrollBarMode = msg.arg2; 7292 break; 7293 7294 default: 7295 super.handleMessage(msg); 7296 break; 7297 } 7298 } 7299 } 7300 7301 /** 7302 * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID 7303 * and UPDATE_TEXT_SELECTION_MSG_ID. Update the selection of WebTextView. 7304 */ 7305 private void updateTextSelectionFromMessage(int nodePointer, 7306 int textGeneration, WebViewCore.TextSelectionData data) { 7307 if (inEditingMode() 7308 && mWebTextView.isSameTextField(nodePointer) 7309 && textGeneration == mTextGeneration) { 7310 mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd); 7311 } 7312 } 7313 7314 // Class used to use a dropdown for a <select> element 7315 private class InvokeListBox implements Runnable { 7316 // Whether the listbox allows multiple selection. 7317 private boolean mMultiple; 7318 // Passed in to a list with multiple selection to tell 7319 // which items are selected. 7320 private int[] mSelectedArray; 7321 // Passed in to a list with single selection to tell 7322 // where the initial selection is. 7323 private int mSelection; 7324 7325 private Container[] mContainers; 7326 7327 // Need these to provide stable ids to my ArrayAdapter, 7328 // which normally does not have stable ids. (Bug 1250098) 7329 private class Container extends Object { 7330 /** 7331 * Possible values for mEnabled. Keep in sync with OptionStatus in 7332 * WebViewCore.cpp 7333 */ 7334 final static int OPTGROUP = -1; 7335 final static int OPTION_DISABLED = 0; 7336 final static int OPTION_ENABLED = 1; 7337 7338 String mString; 7339 int mEnabled; 7340 int mId; 7341 7342 public String toString() { 7343 return mString; 7344 } 7345 } 7346 7347 /** 7348 * Subclass ArrayAdapter so we can disable OptionGroupLabels, 7349 * and allow filtering. 7350 */ 7351 private class MyArrayListAdapter extends ArrayAdapter<Container> { 7352 public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { 7353 super(context, 7354 multiple ? com.android.internal.R.layout.select_dialog_multichoice : 7355 com.android.internal.R.layout.select_dialog_singlechoice, 7356 objects); 7357 } 7358 7359 @Override 7360 public View getView(int position, View convertView, 7361 ViewGroup parent) { 7362 // Always pass in null so that we will get a new CheckedTextView 7363 // Otherwise, an item which was previously used as an <optgroup> 7364 // element (i.e. has no check), could get used as an <option> 7365 // element, which needs a checkbox/radio, but it would not have 7366 // one. 7367 convertView = super.getView(position, null, parent); 7368 Container c = item(position); 7369 if (c != null && Container.OPTION_ENABLED != c.mEnabled) { 7370 // ListView does not draw dividers between disabled and 7371 // enabled elements. Use a LinearLayout to provide dividers 7372 LinearLayout layout = new LinearLayout(mContext); 7373 layout.setOrientation(LinearLayout.VERTICAL); 7374 if (position > 0) { 7375 View dividerTop = new View(mContext); 7376 dividerTop.setBackgroundResource( 7377 android.R.drawable.divider_horizontal_bright); 7378 layout.addView(dividerTop); 7379 } 7380 7381 if (Container.OPTGROUP == c.mEnabled) { 7382 // Currently select_dialog_multichoice and 7383 // select_dialog_singlechoice are CheckedTextViews. If 7384 // that changes, the class cast will no longer be valid. 7385 Assert.assertTrue( 7386 convertView instanceof CheckedTextView); 7387 ((CheckedTextView) convertView).setCheckMarkDrawable( 7388 null); 7389 } else { 7390 // c.mEnabled == Container.OPTION_DISABLED 7391 // Draw the disabled element in a disabled state. 7392 convertView.setEnabled(false); 7393 } 7394 7395 layout.addView(convertView); 7396 if (position < getCount() - 1) { 7397 View dividerBottom = new View(mContext); 7398 dividerBottom.setBackgroundResource( 7399 android.R.drawable.divider_horizontal_bright); 7400 layout.addView(dividerBottom); 7401 } 7402 return layout; 7403 } 7404 return convertView; 7405 } 7406 7407 @Override 7408 public boolean hasStableIds() { 7409 // AdapterView's onChanged method uses this to determine whether 7410 // to restore the old state. Return false so that the old (out 7411 // of date) state does not replace the new, valid state. 7412 return false; 7413 } 7414 7415 private Container item(int position) { 7416 if (position < 0 || position >= getCount()) { 7417 return null; 7418 } 7419 return (Container) getItem(position); 7420 } 7421 7422 @Override 7423 public long getItemId(int position) { 7424 Container item = item(position); 7425 if (item == null) { 7426 return -1; 7427 } 7428 return item.mId; 7429 } 7430 7431 @Override 7432 public boolean areAllItemsEnabled() { 7433 return false; 7434 } 7435 7436 @Override 7437 public boolean isEnabled(int position) { 7438 Container item = item(position); 7439 if (item == null) { 7440 return false; 7441 } 7442 return Container.OPTION_ENABLED == item.mEnabled; 7443 } 7444 } 7445 7446 private InvokeListBox(String[] array, int[] enabled, int[] selected) { 7447 mMultiple = true; 7448 mSelectedArray = selected; 7449 7450 int length = array.length; 7451 mContainers = new Container[length]; 7452 for (int i = 0; i < length; i++) { 7453 mContainers[i] = new Container(); 7454 mContainers[i].mString = array[i]; 7455 mContainers[i].mEnabled = enabled[i]; 7456 mContainers[i].mId = i; 7457 } 7458 } 7459 7460 private InvokeListBox(String[] array, int[] enabled, int selection) { 7461 mSelection = selection; 7462 mMultiple = false; 7463 7464 int length = array.length; 7465 mContainers = new Container[length]; 7466 for (int i = 0; i < length; i++) { 7467 mContainers[i] = new Container(); 7468 mContainers[i].mString = array[i]; 7469 mContainers[i].mEnabled = enabled[i]; 7470 mContainers[i].mId = i; 7471 } 7472 } 7473 7474 /* 7475 * Whenever the data set changes due to filtering, this class ensures 7476 * that the checked item remains checked. 7477 */ 7478 private class SingleDataSetObserver extends DataSetObserver { 7479 private long mCheckedId; 7480 private ListView mListView; 7481 private Adapter mAdapter; 7482 7483 /* 7484 * Create a new observer. 7485 * @param id The ID of the item to keep checked. 7486 * @param l ListView for getting and clearing the checked states 7487 * @param a Adapter for getting the IDs 7488 */ 7489 public SingleDataSetObserver(long id, ListView l, Adapter a) { 7490 mCheckedId = id; 7491 mListView = l; 7492 mAdapter = a; 7493 } 7494 7495 public void onChanged() { 7496 // The filter may have changed which item is checked. Find the 7497 // item that the ListView thinks is checked. 7498 int position = mListView.getCheckedItemPosition(); 7499 long id = mAdapter.getItemId(position); 7500 if (mCheckedId != id) { 7501 // Clear the ListView's idea of the checked item, since 7502 // it is incorrect 7503 mListView.clearChoices(); 7504 // Search for mCheckedId. If it is in the filtered list, 7505 // mark it as checked 7506 int count = mAdapter.getCount(); 7507 for (int i = 0; i < count; i++) { 7508 if (mAdapter.getItemId(i) == mCheckedId) { 7509 mListView.setItemChecked(i, true); 7510 break; 7511 } 7512 } 7513 } 7514 } 7515 7516 public void onInvalidate() {} 7517 } 7518 7519 public void run() { 7520 final ListView listView = (ListView) LayoutInflater.from(mContext) 7521 .inflate(com.android.internal.R.layout.select_dialog, null); 7522 final MyArrayListAdapter adapter = new 7523 MyArrayListAdapter(mContext, mContainers, mMultiple); 7524 AlertDialog.Builder b = new AlertDialog.Builder(mContext) 7525 .setView(listView).setCancelable(true) 7526 .setInverseBackgroundForced(true); 7527 7528 if (mMultiple) { 7529 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 7530 public void onClick(DialogInterface dialog, int which) { 7531 mWebViewCore.sendMessage( 7532 EventHub.LISTBOX_CHOICES, 7533 adapter.getCount(), 0, 7534 listView.getCheckedItemPositions()); 7535 }}); 7536 b.setNegativeButton(android.R.string.cancel, 7537 new DialogInterface.OnClickListener() { 7538 public void onClick(DialogInterface dialog, int which) { 7539 mWebViewCore.sendMessage( 7540 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 7541 }}); 7542 } 7543 final AlertDialog dialog = b.create(); 7544 listView.setAdapter(adapter); 7545 listView.setFocusableInTouchMode(true); 7546 // There is a bug (1250103) where the checks in a ListView with 7547 // multiple items selected are associated with the positions, not 7548 // the ids, so the items do not properly retain their checks when 7549 // filtered. Do not allow filtering on multiple lists until 7550 // that bug is fixed. 7551 7552 listView.setTextFilterEnabled(!mMultiple); 7553 if (mMultiple) { 7554 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 7555 int length = mSelectedArray.length; 7556 for (int i = 0; i < length; i++) { 7557 listView.setItemChecked(mSelectedArray[i], true); 7558 } 7559 } else { 7560 listView.setOnItemClickListener(new OnItemClickListener() { 7561 public void onItemClick(AdapterView parent, View v, 7562 int position, long id) { 7563 mWebViewCore.sendMessage( 7564 EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0); 7565 dialog.dismiss(); 7566 } 7567 }); 7568 if (mSelection != -1) { 7569 listView.setSelection(mSelection); 7570 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 7571 listView.setItemChecked(mSelection, true); 7572 DataSetObserver observer = new SingleDataSetObserver( 7573 adapter.getItemId(mSelection), listView, adapter); 7574 adapter.registerDataSetObserver(observer); 7575 } 7576 } 7577 dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 7578 public void onCancel(DialogInterface dialog) { 7579 mWebViewCore.sendMessage( 7580 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 7581 } 7582 }); 7583 dialog.show(); 7584 } 7585 } 7586 7587 /* 7588 * Request a dropdown menu for a listbox with multiple selection. 7589 * 7590 * @param array Labels for the listbox. 7591 * @param enabledArray State for each element in the list. See static 7592 * integers in Container class. 7593 * @param selectedArray Which positions are initally selected. 7594 */ 7595 void requestListBox(String[] array, int[] enabledArray, int[] 7596 selectedArray) { 7597 mPrivateHandler.post( 7598 new InvokeListBox(array, enabledArray, selectedArray)); 7599 } 7600 7601 private void updateZoomRange(WebViewCore.RestoreState restoreState, 7602 int viewWidth, int minPrefWidth, boolean updateZoomOverview) { 7603 if (restoreState.mMinScale == 0) { 7604 if (restoreState.mMobileSite) { 7605 if (minPrefWidth > Math.max(0, viewWidth)) { 7606 mMinZoomScale = (float) viewWidth / minPrefWidth; 7607 mMinZoomScaleFixed = false; 7608 if (updateZoomOverview) { 7609 WebSettings settings = getSettings(); 7610 mInZoomOverview = settings.getUseWideViewPort() && 7611 settings.getLoadWithOverviewMode(); 7612 } 7613 } else { 7614 mMinZoomScale = restoreState.mDefaultScale; 7615 mMinZoomScaleFixed = true; 7616 } 7617 } else { 7618 mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; 7619 mMinZoomScaleFixed = false; 7620 } 7621 } else { 7622 mMinZoomScale = restoreState.mMinScale; 7623 mMinZoomScaleFixed = true; 7624 } 7625 if (restoreState.mMaxScale == 0) { 7626 mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; 7627 } else { 7628 mMaxZoomScale = restoreState.mMaxScale; 7629 } 7630 } 7631 7632 /* 7633 * Request a dropdown menu for a listbox with single selection or a single 7634 * <select> element. 7635 * 7636 * @param array Labels for the listbox. 7637 * @param enabledArray State for each element in the list. See static 7638 * integers in Container class. 7639 * @param selection Which position is initally selected. 7640 */ 7641 void requestListBox(String[] array, int[] enabledArray, int selection) { 7642 mPrivateHandler.post( 7643 new InvokeListBox(array, enabledArray, selection)); 7644 } 7645 7646 // called by JNI 7647 private void sendMoveFocus(int frame, int node) { 7648 mWebViewCore.sendMessage(EventHub.SET_MOVE_FOCUS, 7649 new WebViewCore.CursorData(frame, node, 0, 0)); 7650 } 7651 7652 // called by JNI 7653 private void sendMoveMouse(int frame, int node, int x, int y) { 7654 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, 7655 new WebViewCore.CursorData(frame, node, x, y)); 7656 } 7657 7658 /* 7659 * Send a mouse move event to the webcore thread. 7660 * 7661 * @param removeFocus Pass true if the "mouse" cursor is now over a node 7662 * which wants key events, but it is not the focus. This 7663 * will make the visual appear as though nothing is in 7664 * focus. Remove the WebTextView, if present, and stop 7665 * drawing the blinking caret. 7666 * called by JNI 7667 */ 7668 private void sendMoveMouseIfLatest(boolean removeFocus) { 7669 if (removeFocus) { 7670 clearTextEntry(true); 7671 } 7672 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, 7673 cursorData()); 7674 } 7675 7676 // called by JNI 7677 private void sendMotionUp(int touchGeneration, 7678 int frame, int node, int x, int y) { 7679 WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); 7680 touchUpData.mMoveGeneration = touchGeneration; 7681 touchUpData.mFrame = frame; 7682 touchUpData.mNode = node; 7683 touchUpData.mX = x; 7684 touchUpData.mY = y; 7685 mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); 7686 } 7687 7688 7689 private int getScaledMaxXScroll() { 7690 int width; 7691 if (mHeightCanMeasure == false) { 7692 width = getViewWidth() / 4; 7693 } else { 7694 Rect visRect = new Rect(); 7695 calcOurVisibleRect(visRect); 7696 width = visRect.width() / 2; 7697 } 7698 // FIXME the divisor should be retrieved from somewhere 7699 return viewToContentX(width); 7700 } 7701 7702 private int getScaledMaxYScroll() { 7703 int height; 7704 if (mHeightCanMeasure == false) { 7705 height = getViewHeight() / 4; 7706 } else { 7707 Rect visRect = new Rect(); 7708 calcOurVisibleRect(visRect); 7709 height = visRect.height() / 2; 7710 } 7711 // FIXME the divisor should be retrieved from somewhere 7712 // the closest thing today is hard-coded into ScrollView.java 7713 // (from ScrollView.java, line 363) int maxJump = height/2; 7714 return Math.round(height * mInvActualScale); 7715 } 7716 7717 /** 7718 * Called by JNI to invalidate view 7719 */ 7720 private void viewInvalidate() { 7721 invalidate(); 7722 } 7723 7724 /** 7725 * Pass the key to the plugin. This assumes that nativeFocusIsPlugin() 7726 * returned true. 7727 */ 7728 private void letPluginHandleNavKey(int keyCode, long time, boolean down) { 7729 int keyEventAction; 7730 int eventHubAction; 7731 if (down) { 7732 keyEventAction = KeyEvent.ACTION_DOWN; 7733 eventHubAction = EventHub.KEY_DOWN; 7734 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 7735 } else { 7736 keyEventAction = KeyEvent.ACTION_UP; 7737 eventHubAction = EventHub.KEY_UP; 7738 } 7739 KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode, 7740 1, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0) 7741 | (false ? KeyEvent.META_ALT_ON : 0) // FIXME 7742 | (false ? KeyEvent.META_SYM_ON : 0) // FIXME 7743 , 0, 0, 0); 7744 mWebViewCore.sendMessage(eventHubAction, event); 7745 } 7746 7747 // return true if the key was handled 7748 private boolean navHandledKey(int keyCode, int count, boolean noScroll, 7749 long time) { 7750 if (mNativeClass == 0) { 7751 return false; 7752 } 7753 mLastCursorTime = time; 7754 mLastCursorBounds = nativeGetCursorRingBounds(); 7755 boolean keyHandled 7756 = nativeMoveCursor(keyCode, count, noScroll) == false; 7757 if (DebugFlags.WEB_VIEW) { 7758 Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds 7759 + " mLastCursorTime=" + mLastCursorTime 7760 + " handled=" + keyHandled); 7761 } 7762 if (keyHandled == false || mHeightCanMeasure == false) { 7763 return keyHandled; 7764 } 7765 Rect contentCursorRingBounds = nativeGetCursorRingBounds(); 7766 if (contentCursorRingBounds.isEmpty()) return keyHandled; 7767 Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds); 7768 Rect visRect = new Rect(); 7769 calcOurVisibleRect(visRect); 7770 Rect outset = new Rect(visRect); 7771 int maxXScroll = visRect.width() / 2; 7772 int maxYScroll = visRect.height() / 2; 7773 outset.inset(-maxXScroll, -maxYScroll); 7774 if (Rect.intersects(outset, viewCursorRingBounds) == false) { 7775 return keyHandled; 7776 } 7777 // FIXME: Necessary because ScrollView/ListView do not scroll left/right 7778 int maxH = Math.min(viewCursorRingBounds.right - visRect.right, 7779 maxXScroll); 7780 if (maxH > 0) { 7781 pinScrollBy(maxH, 0, true, 0); 7782 } else { 7783 maxH = Math.max(viewCursorRingBounds.left - visRect.left, 7784 -maxXScroll); 7785 if (maxH < 0) { 7786 pinScrollBy(maxH, 0, true, 0); 7787 } 7788 } 7789 if (mLastCursorBounds.isEmpty()) return keyHandled; 7790 if (mLastCursorBounds.equals(contentCursorRingBounds)) { 7791 return keyHandled; 7792 } 7793 if (DebugFlags.WEB_VIEW) { 7794 Log.v(LOGTAG, "navHandledKey contentCursorRingBounds=" 7795 + contentCursorRingBounds); 7796 } 7797 requestRectangleOnScreen(viewCursorRingBounds); 7798 mUserScroll = true; 7799 return keyHandled; 7800 } 7801 7802 /** 7803 * Set the background color. It's white by default. Pass 7804 * zero to make the view transparent. 7805 * @param color the ARGB color described by Color.java 7806 */ 7807 public void setBackgroundColor(int color) { 7808 mBackgroundColor = color; 7809 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color); 7810 } 7811 7812 public void debugDump() { 7813 nativeDebugDump(); 7814 mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); 7815 } 7816 7817 /** 7818 * Draw the HTML page into the specified canvas. This call ignores any 7819 * view-specific zoom, scroll offset, or other changes. It does not draw 7820 * any view-specific chrome, such as progress or URL bars. 7821 * 7822 * @hide only needs to be accessible to Browser and testing 7823 */ 7824 public void drawPage(Canvas canvas) { 7825 mWebViewCore.drawContentPicture(canvas, 0, false, false); 7826 } 7827 7828 /** 7829 * Set the time to wait between passing touches to WebCore. See also the 7830 * TOUCH_SENT_INTERVAL member for further discussion. 7831 * 7832 * @hide This is only used by the DRT test application. 7833 */ 7834 public void setTouchInterval(int interval) { 7835 mCurrentTouchInterval = interval; 7836 } 7837 7838 /** 7839 * Update our cache with updatedText. 7840 * @param updatedText The new text to put in our cache. 7841 */ 7842 /* package */ void updateCachedTextfield(String updatedText) { 7843 // Also place our generation number so that when we look at the cache 7844 // we recognize that it is up to date. 7845 nativeUpdateCachedTextfield(updatedText, mTextGeneration); 7846 } 7847 7848 /* package */ ViewManager getViewManager() { 7849 return mViewManager; 7850 } 7851 7852 private native int nativeCacheHitFramePointer(); 7853 private native Rect nativeCacheHitNodeBounds(); 7854 private native int nativeCacheHitNodePointer(); 7855 /* package */ native void nativeClearCursor(); 7856 private native void nativeCreate(int ptr); 7857 private native int nativeCursorFramePointer(); 7858 private native Rect nativeCursorNodeBounds(); 7859 private native int nativeCursorNodePointer(); 7860 /* package */ native boolean nativeCursorMatchesFocus(); 7861 private native boolean nativeCursorIntersects(Rect visibleRect); 7862 private native boolean nativeCursorIsAnchor(); 7863 private native boolean nativeCursorIsTextInput(); 7864 private native Point nativeCursorPosition(); 7865 private native String nativeCursorText(); 7866 /** 7867 * Returns true if the native cursor node says it wants to handle key events 7868 * (ala plugins). This can only be called if mNativeClass is non-zero! 7869 */ 7870 private native boolean nativeCursorWantsKeyEvents(); 7871 private native void nativeDebugDump(); 7872 private native void nativeDestroy(); 7873 private native boolean nativeEvaluateLayersAnimations(); 7874 private native void nativeExtendSelection(int x, int y); 7875 private native void nativeDrawExtras(Canvas canvas, int extra); 7876 private native void nativeDumpDisplayTree(String urlOrNull); 7877 private native int nativeFindAll(String findLower, String findUpper); 7878 private native void nativeFindNext(boolean forward); 7879 /* package */ native int nativeFocusCandidateFramePointer(); 7880 /* package */ native boolean nativeFocusCandidateHasNextTextfield(); 7881 /* package */ native boolean nativeFocusCandidateIsPassword(); 7882 private native boolean nativeFocusCandidateIsRtlText(); 7883 private native boolean nativeFocusCandidateIsTextInput(); 7884 /* package */ native int nativeFocusCandidateMaxLength(); 7885 /* package */ native String nativeFocusCandidateName(); 7886 private native Rect nativeFocusCandidateNodeBounds(); 7887 /** 7888 * @return A Rect with left, top, right, bottom set to the corresponding 7889 * padding values in the focus candidate, if it is a textfield/textarea with 7890 * a style. Otherwise return null. This is not actually a rectangle; Rect 7891 * is being used to pass four integers. 7892 */ 7893 private native Rect nativeFocusCandidatePaddingRect(); 7894 /* package */ native int nativeFocusCandidatePointer(); 7895 private native String nativeFocusCandidateText(); 7896 private native int nativeFocusCandidateTextSize(); 7897 /** 7898 * Returns an integer corresponding to WebView.cpp::type. 7899 * See WebTextView.setType() 7900 */ 7901 private native int nativeFocusCandidateType(); 7902 private native boolean nativeFocusIsPlugin(); 7903 private native Rect nativeFocusNodeBounds(); 7904 /* package */ native int nativeFocusNodePointer(); 7905 private native Rect nativeGetCursorRingBounds(); 7906 private native String nativeGetSelection(); 7907 private native boolean nativeHasCursorNode(); 7908 private native boolean nativeHasFocusNode(); 7909 private native void nativeHideCursor(); 7910 private native boolean nativeHitSelection(int x, int y); 7911 private native String nativeImageURI(int x, int y); 7912 private native void nativeInstrumentReport(); 7913 /* package */ native boolean nativeMoveCursorToNextTextInput(); 7914 // return true if the page has been scrolled 7915 private native boolean nativeMotionUp(int x, int y, int slop); 7916 // returns false if it handled the key 7917 private native boolean nativeMoveCursor(int keyCode, int count, 7918 boolean noScroll); 7919 private native int nativeMoveGeneration(); 7920 private native void nativeMoveSelection(int x, int y); 7921 private native boolean nativePointInNavCache(int x, int y, int slop); 7922 // Like many other of our native methods, you must make sure that 7923 // mNativeClass is not null before calling this method. 7924 private native void nativeRecordButtons(boolean focused, 7925 boolean pressed, boolean invalidate); 7926 private native void nativeResetSelection(); 7927 private native void nativeSelectAll(); 7928 private native void nativeSelectBestAt(Rect rect); 7929 private native int nativeSelectionX(); 7930 private native int nativeSelectionY(); 7931 private native int nativeFindIndex(); 7932 private native void nativeSetExtendSelection(); 7933 private native void nativeSetFindIsEmpty(); 7934 private native void nativeSetFindIsUp(boolean isUp); 7935 private native void nativeSetFollowedLink(boolean followed); 7936 private native void nativeSetHeightCanMeasure(boolean measure); 7937 private native void nativeSetRootLayer(int layer); 7938 private native void nativeSetSelectionPointer(boolean set, 7939 float scale, int x, int y); 7940 private native boolean nativeStartSelection(int x, int y); 7941 private native void nativeSetSelectionRegion(boolean set); 7942 private native Rect nativeSubtractLayers(Rect content); 7943 private native int nativeTextGeneration(); 7944 // Never call this version except by updateCachedTextfield(String) - 7945 // we always want to pass in our generation number. 7946 private native void nativeUpdateCachedTextfield(String updatedText, 7947 int generation); 7948 private native boolean nativeWordSelection(int x, int y); 7949 // return NO_LEFTEDGE means failure. 7950 private static final int NO_LEFTEDGE = -1; 7951 private native int nativeGetBlockLeftEdge(int x, int y, float scale); 7952 } 7953