WebView.java revision 48b99ace195035286df604636f04860b2a0ee1f4
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 17package android.webkit; 18 19import com.android.internal.R; 20 21import android.annotation.Widget; 22import android.app.AlertDialog; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.DialogInterface.OnCancelListener; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.content.res.Resources; 29import android.database.DataSetObserver; 30import android.graphics.Bitmap; 31import android.graphics.BitmapFactory; 32import android.graphics.BitmapShader; 33import android.graphics.Canvas; 34import android.graphics.Color; 35import android.graphics.Interpolator; 36import android.graphics.Paint; 37import android.graphics.Picture; 38import android.graphics.Point; 39import android.graphics.Rect; 40import android.graphics.RectF; 41import android.graphics.Region; 42import android.graphics.Shader; 43import android.graphics.drawable.Drawable; 44import android.net.Uri; 45import android.net.http.SslCertificate; 46import android.os.Bundle; 47import android.os.Handler; 48import android.os.Message; 49import android.os.ServiceManager; 50import android.os.SystemClock; 51import android.text.IClipboard; 52import android.text.Selection; 53import android.text.Spannable; 54import android.util.AttributeSet; 55import android.util.EventLog; 56import android.util.Log; 57import android.util.TypedValue; 58import android.view.Gravity; 59import android.view.KeyEvent; 60import android.view.LayoutInflater; 61import android.view.MotionEvent; 62import android.view.ScaleGestureDetector; 63import android.view.SoundEffectConstants; 64import android.view.VelocityTracker; 65import android.view.View; 66import android.view.ViewConfiguration; 67import android.view.ViewGroup; 68import android.view.ViewTreeObserver; 69import android.view.animation.AlphaAnimation; 70import android.view.inputmethod.EditorInfo; 71import android.view.inputmethod.InputConnection; 72import android.view.inputmethod.InputMethodManager; 73import android.webkit.WebTextView.AutoCompleteAdapter; 74import android.webkit.WebViewCore.EventHub; 75import android.webkit.WebViewCore.TouchEventData; 76import android.widget.AbsoluteLayout; 77import android.widget.Adapter; 78import android.widget.AdapterView; 79import android.widget.AdapterView.OnItemClickListener; 80import android.widget.ArrayAdapter; 81import android.widget.CheckedTextView; 82import android.widget.EdgeGlow; 83import android.widget.FrameLayout; 84import android.widget.LinearLayout; 85import android.widget.ListView; 86import android.widget.OverScroller; 87import android.widget.Toast; 88import android.widget.ZoomButtonsController; 89import android.widget.ZoomControls; 90 91import java.io.File; 92import java.io.FileInputStream; 93import java.io.FileNotFoundException; 94import java.io.FileOutputStream; 95import java.net.URLDecoder; 96import java.util.ArrayList; 97import java.util.HashMap; 98import java.util.List; 99import java.util.Map; 100import java.util.Set; 101 102import 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 304public 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 mWebViewCore.signalRepaintDone(); 3535 } 3536 3537 @Override 3538 public void draw(Canvas canvas) { 3539 super.draw(canvas); 3540 if (mEdgeGlowTop != null && drawEdgeGlows(canvas)) { 3541 invalidate(); 3542 } 3543 } 3544 3545 /** 3546 * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null. 3547 * 3548 * @param canvas Canvas to draw into, transformed into view coordinates. 3549 * @return true if glow effects are still animating and the view should invalidate again. 3550 */ 3551 private boolean drawEdgeGlows(Canvas canvas) { 3552 final int scrollX = mScrollX; 3553 final int scrollY = mScrollY; 3554 final int width = getWidth(); 3555 int height = getHeight(); 3556 3557 boolean invalidateForGlow = false; 3558 if (!mEdgeGlowTop.isFinished()) { 3559 final int restoreCount = canvas.save(); 3560 3561 canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY)); 3562 mEdgeGlowTop.setSize(width * 2, height); 3563 invalidateForGlow |= mEdgeGlowTop.draw(canvas); 3564 canvas.restoreToCount(restoreCount); 3565 } 3566 if (!mEdgeGlowBottom.isFinished()) { 3567 final int restoreCount = canvas.save(); 3568 3569 canvas.translate(-width / 2 + scrollX, Math.max(computeMaxScrollY(), scrollY) + height); 3570 canvas.rotate(180, width, 0); 3571 mEdgeGlowBottom.setSize(width * 2, height); 3572 invalidateForGlow |= mEdgeGlowBottom.draw(canvas); 3573 canvas.restoreToCount(restoreCount); 3574 } 3575 if (!mEdgeGlowLeft.isFinished()) { 3576 final int restoreCount = canvas.save(); 3577 3578 canvas.rotate(270); 3579 canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX)); 3580 mEdgeGlowLeft.setSize(height * 2, width); 3581 invalidateForGlow |= mEdgeGlowLeft.draw(canvas); 3582 canvas.restoreToCount(restoreCount); 3583 } 3584 if (!mEdgeGlowRight.isFinished()) { 3585 final int restoreCount = canvas.save(); 3586 3587 canvas.rotate(90); 3588 canvas.translate(-height / 2 + scrollY, 3589 -(Math.max(computeMaxScrollX(), scrollX) + width)); 3590 mEdgeGlowRight.setSize(height * 2, width); 3591 invalidateForGlow |= mEdgeGlowRight.draw(canvas); 3592 canvas.restoreToCount(restoreCount); 3593 } 3594 return invalidateForGlow; 3595 } 3596 3597 @Override 3598 public void setLayoutParams(ViewGroup.LayoutParams params) { 3599 if (params.height == LayoutParams.WRAP_CONTENT) { 3600 mWrapContent = true; 3601 } 3602 super.setLayoutParams(params); 3603 } 3604 3605 @Override 3606 public boolean performLongClick() { 3607 // performLongClick() is the result of a delayed message. If we switch 3608 // to windows overview, the WebView will be temporarily removed from the 3609 // view system. In that case, do nothing. 3610 if (getParent() == null) return false; 3611 if (mNativeClass != 0 && nativeCursorIsTextInput()) { 3612 // Send the click so that the textfield is in focus 3613 centerKeyPressOnTextField(); 3614 rebuildWebTextView(); 3615 } else { 3616 clearTextEntry(true); 3617 } 3618 if (inEditingMode()) { 3619 return mWebTextView.performLongClick(); 3620 } 3621 /* if long click brings up a context menu, the super function 3622 * returns true and we're done. Otherwise, nothing happened when 3623 * the user clicked. */ 3624 if (super.performLongClick()) { 3625 return true; 3626 } 3627 /* In the case where the application hasn't already handled the long 3628 * click action, look for a word under the click. If one is found, 3629 * animate the text selection into view. 3630 * FIXME: no animation code yet */ 3631 if (mSelectingText) return false; // long click does nothing on selection 3632 int x = viewToContentX((int) mLastTouchX + mScrollX); 3633 int y = viewToContentY((int) mLastTouchY + mScrollY); 3634 setUpSelect(); 3635 if (mNativeClass != 0 && nativeWordSelection(x, y)) { 3636 nativeSetExtendSelection(); 3637 WebChromeClient client = getWebChromeClient(); 3638 if (client != null) client.onSelectionStart(this); 3639 return true; 3640 } 3641 notifySelectDialogDismissed(); 3642 return false; 3643 } 3644 3645 boolean inAnimateZoom() { 3646 return mZoomScale != 0; 3647 } 3648 3649 /** 3650 * Need to adjust the WebTextView after a change in zoom, since mActualScale 3651 * has changed. This is especially important for password fields, which are 3652 * drawn by the WebTextView, since it conveys more information than what 3653 * webkit draws. Thus we need to reposition it to show in the correct 3654 * place. 3655 */ 3656 private boolean mNeedToAdjustWebTextView; 3657 3658 private boolean didUpdateTextViewBounds(boolean allowIntersect) { 3659 Rect contentBounds = nativeFocusCandidateNodeBounds(); 3660 Rect vBox = contentToViewRect(contentBounds); 3661 Rect visibleRect = new Rect(); 3662 calcOurVisibleRect(visibleRect); 3663 // If the textfield is on screen, place the WebTextView in 3664 // its new place, accounting for our new scroll/zoom values, 3665 // and adjust its textsize. 3666 if (allowIntersect ? Rect.intersects(visibleRect, vBox) 3667 : visibleRect.contains(vBox)) { 3668 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), 3669 vBox.height()); 3670 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 3671 contentToViewDimension( 3672 nativeFocusCandidateTextSize())); 3673 return true; 3674 } else { 3675 // The textfield is now off screen. The user probably 3676 // was not zooming to see the textfield better. Remove 3677 // the WebTextView. If the user types a key, and the 3678 // textfield is still in focus, we will reconstruct 3679 // the WebTextView and scroll it back on screen. 3680 mWebTextView.remove(); 3681 return false; 3682 } 3683 } 3684 3685 private void drawExtras(Canvas canvas, int extras, boolean animationsRunning) { 3686 // If mNativeClass is 0, we should not reach here, so we do not 3687 // need to check it again. 3688 if (animationsRunning) { 3689 canvas.setDrawFilter(mWebViewCore.mZoomFilter); 3690 } 3691 nativeDrawExtras(canvas, extras); 3692 canvas.setDrawFilter(null); 3693 } 3694 3695 private void drawCoreAndCursorRing(Canvas canvas, int color, 3696 boolean drawCursorRing) { 3697 if (mDrawHistory) { 3698 canvas.scale(mActualScale, mActualScale); 3699 canvas.drawPicture(mHistoryPicture); 3700 return; 3701 } 3702 3703 boolean animateZoom = mZoomScale != 0; 3704 boolean animateScroll = ((!mScroller.isFinished() 3705 || mVelocityTracker != null) 3706 && (mTouchMode != TOUCH_DRAG_MODE || 3707 mHeldMotionless != MOTIONLESS_TRUE)) 3708 || mDeferTouchMode == TOUCH_DRAG_MODE; 3709 if (mTouchMode == TOUCH_DRAG_MODE) { 3710 if (mHeldMotionless == MOTIONLESS_PENDING) { 3711 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 3712 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 3713 mHeldMotionless = MOTIONLESS_FALSE; 3714 } 3715 if (mHeldMotionless == MOTIONLESS_FALSE) { 3716 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3717 .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME); 3718 mHeldMotionless = MOTIONLESS_PENDING; 3719 } 3720 } 3721 if (animateZoom) { 3722 float zoomScale; 3723 int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); 3724 if (interval < ZOOM_ANIMATION_LENGTH) { 3725 float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; 3726 zoomScale = 1.0f / (mInvInitialZoomScale 3727 + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); 3728 invalidate(); 3729 } else { 3730 zoomScale = mZoomScale; 3731 // set mZoomScale to be 0 as we have done animation 3732 mZoomScale = 0; 3733 WebViewCore.resumeUpdatePicture(mWebViewCore); 3734 // call invalidate() again to draw with the final filters 3735 invalidate(); 3736 if (mNeedToAdjustWebTextView) { 3737 mNeedToAdjustWebTextView = false; 3738 if (didUpdateTextViewBounds(false) 3739 && nativeFocusCandidateIsPassword()) { 3740 // If it is a password field, start drawing the 3741 // WebTextView once again. 3742 mWebTextView.setInPassword(true); 3743 } 3744 } 3745 } 3746 // calculate the intermediate scroll position. As we need to use 3747 // zoomScale, we can't use pinLocX/Y directly. Copy the logic here. 3748 float scale = zoomScale * mInvInitialZoomScale; 3749 int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) 3750 - mZoomCenterX); 3751 tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth 3752 * zoomScale)) + mScrollX; 3753 int titleHeight = getTitleHeight(); 3754 int ty = Math.round(scale 3755 * (mInitialScrollY + mZoomCenterY - titleHeight) 3756 - (mZoomCenterY - titleHeight)); 3757 ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty 3758 - titleHeight, getViewHeight(), Math.round(mContentHeight 3759 * zoomScale)) + titleHeight) + mScrollY; 3760 canvas.translate(tx, ty); 3761 canvas.scale(zoomScale, zoomScale); 3762 if (inEditingMode() && !mNeedToAdjustWebTextView 3763 && mZoomScale != 0) { 3764 // The WebTextView is up. Keep track of this so we can adjust 3765 // its size and placement when we finish zooming 3766 mNeedToAdjustWebTextView = true; 3767 // If it is in password mode, turn it off so it does not draw 3768 // misplaced. 3769 if (nativeFocusCandidateIsPassword()) { 3770 mWebTextView.setInPassword(false); 3771 } 3772 } 3773 } else { 3774 canvas.scale(mActualScale, mActualScale); 3775 } 3776 3777 boolean UIAnimationsRunning = false; 3778 // Currently for each draw we compute the animation values; 3779 // We may in the future decide to do that independently. 3780 if (mNativeClass != 0 && nativeEvaluateLayersAnimations()) { 3781 UIAnimationsRunning = true; 3782 // If we have unfinished (or unstarted) animations, 3783 // we ask for a repaint. 3784 invalidate(); 3785 } 3786 mWebViewCore.drawContentPicture(canvas, color, 3787 (animateZoom || mPreviewZoomOnly || UIAnimationsRunning), 3788 animateScroll); 3789 if (mNativeClass == 0) return; 3790 // decide which adornments to draw 3791 int extras = DRAW_EXTRAS_NONE; 3792 if (mFindIsUp) { 3793 extras = DRAW_EXTRAS_FIND; 3794 } else if (mSelectingText) { 3795 extras = DRAW_EXTRAS_SELECTION; 3796 nativeSetSelectionPointer(mDrawSelectionPointer, 3797 mInvActualScale, 3798 mSelectX, mSelectY - getTitleHeight()); 3799 } else if (drawCursorRing) { 3800 extras = DRAW_EXTRAS_CURSOR_RING; 3801 } 3802 drawExtras(canvas, extras, UIAnimationsRunning); 3803 3804 if (extras == DRAW_EXTRAS_CURSOR_RING) { 3805 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 3806 mTouchMode = TOUCH_SHORTPRESS_MODE; 3807 } 3808 } 3809 if (mFocusSizeChanged) { 3810 mFocusSizeChanged = false; 3811 // If we are zooming, this will get handled above, when the zoom 3812 // finishes. We also do not need to do this unless the WebTextView 3813 // is showing. 3814 if (!animateZoom && inEditingMode()) { 3815 didUpdateTextViewBounds(true); 3816 } 3817 } 3818 } 3819 3820 // draw history 3821 private boolean mDrawHistory = false; 3822 private Picture mHistoryPicture = null; 3823 private int mHistoryWidth = 0; 3824 private int mHistoryHeight = 0; 3825 3826 // Only check the flag, can be called from WebCore thread 3827 boolean drawHistory() { 3828 return mDrawHistory; 3829 } 3830 3831 // Should only be called in UI thread 3832 void switchOutDrawHistory() { 3833 if (null == mWebViewCore) return; // CallbackProxy may trigger this 3834 if (mDrawHistory && mWebViewCore.pictureReady()) { 3835 mDrawHistory = false; 3836 mHistoryPicture = null; 3837 invalidate(); 3838 int oldScrollX = mScrollX; 3839 int oldScrollY = mScrollY; 3840 mScrollX = pinLocX(mScrollX); 3841 mScrollY = pinLocY(mScrollY); 3842 if (oldScrollX != mScrollX || oldScrollY != mScrollY) { 3843 mUserScroll = false; 3844 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX, 3845 oldScrollY); 3846 onScrollChanged(mScrollX, mScrollY, oldScrollX, oldScrollY); 3847 } else { 3848 sendOurVisibleRect(); 3849 } 3850 } 3851 } 3852 3853 WebViewCore.CursorData cursorData() { 3854 WebViewCore.CursorData result = new WebViewCore.CursorData(); 3855 result.mMoveGeneration = nativeMoveGeneration(); 3856 result.mFrame = nativeCursorFramePointer(); 3857 Point position = nativeCursorPosition(); 3858 result.mX = position.x; 3859 result.mY = position.y; 3860 return result; 3861 } 3862 3863 /** 3864 * Delete text from start to end in the focused textfield. If there is no 3865 * focus, or if start == end, silently fail. If start and end are out of 3866 * order, swap them. 3867 * @param start Beginning of selection to delete. 3868 * @param end End of selection to delete. 3869 */ 3870 /* package */ void deleteSelection(int start, int end) { 3871 mTextGeneration++; 3872 WebViewCore.TextSelectionData data 3873 = new WebViewCore.TextSelectionData(start, end); 3874 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0, 3875 data); 3876 } 3877 3878 /** 3879 * Set the selection to (start, end) in the focused textfield. If start and 3880 * end are out of order, swap them. 3881 * @param start Beginning of selection. 3882 * @param end End of selection. 3883 */ 3884 /* package */ void setSelection(int start, int end) { 3885 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end); 3886 } 3887 3888 @Override 3889 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 3890 InputConnection connection = super.onCreateInputConnection(outAttrs); 3891 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN; 3892 return connection; 3893 } 3894 3895 /** 3896 * Called in response to a message from webkit telling us that the soft 3897 * keyboard should be launched. 3898 */ 3899 private void displaySoftKeyboard(boolean isTextView) { 3900 InputMethodManager imm = (InputMethodManager) 3901 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 3902 3903 // bring it back to the default scale so that user can enter text 3904 boolean zoom = mActualScale < mDefaultScale; 3905 if (zoom) { 3906 mInZoomOverview = false; 3907 mZoomCenterX = mLastTouchX; 3908 mZoomCenterY = mLastTouchY; 3909 // do not change text wrap scale so that there is no reflow 3910 setNewZoomScale(mDefaultScale, false, false); 3911 } 3912 if (isTextView) { 3913 rebuildWebTextView(); 3914 if (inEditingMode()) { 3915 imm.showSoftInput(mWebTextView, 0); 3916 if (zoom) { 3917 didUpdateTextViewBounds(true); 3918 } 3919 return; 3920 } 3921 } 3922 // Used by plugins. 3923 // Also used if the navigation cache is out of date, and 3924 // does not recognize that a textfield is in focus. In that 3925 // case, use WebView as the targeted view. 3926 // see http://b/issue?id=2457459 3927 imm.showSoftInput(this, 0); 3928 } 3929 3930 // Called by WebKit to instruct the UI to hide the keyboard 3931 private void hideSoftKeyboard() { 3932 InputMethodManager imm = (InputMethodManager) 3933 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 3934 3935 imm.hideSoftInputFromWindow(this.getWindowToken(), 0); 3936 } 3937 3938 /* 3939 * This method checks the current focus and cursor and potentially rebuilds 3940 * mWebTextView to have the appropriate properties, such as password, 3941 * multiline, and what text it contains. It also removes it if necessary. 3942 */ 3943 /* package */ void rebuildWebTextView() { 3944 // If the WebView does not have focus, do nothing until it gains focus. 3945 if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) { 3946 return; 3947 } 3948 boolean alreadyThere = inEditingMode(); 3949 // inEditingMode can only return true if mWebTextView is non-null, 3950 // so we can safely call remove() if (alreadyThere) 3951 if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) { 3952 if (alreadyThere) { 3953 mWebTextView.remove(); 3954 } 3955 return; 3956 } 3957 // At this point, we know we have found an input field, so go ahead 3958 // and create the WebTextView if necessary. 3959 if (mWebTextView == null) { 3960 mWebTextView = new WebTextView(mContext, WebView.this); 3961 // Initialize our generation number. 3962 mTextGeneration = 0; 3963 } 3964 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 3965 contentToViewDimension(nativeFocusCandidateTextSize())); 3966 Rect visibleRect = new Rect(); 3967 calcOurContentVisibleRect(visibleRect); 3968 // Note that sendOurVisibleRect calls viewToContent, so the coordinates 3969 // should be in content coordinates. 3970 Rect bounds = nativeFocusCandidateNodeBounds(); 3971 Rect vBox = contentToViewRect(bounds); 3972 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height()); 3973 if (!Rect.intersects(bounds, visibleRect)) { 3974 mWebTextView.bringIntoView(); 3975 } 3976 String text = nativeFocusCandidateText(); 3977 int nodePointer = nativeFocusCandidatePointer(); 3978 if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) { 3979 // It is possible that we have the same textfield, but it has moved, 3980 // i.e. In the case of opening/closing the screen. 3981 // In that case, we need to set the dimensions, but not the other 3982 // aspects. 3983 // If the text has been changed by webkit, update it. However, if 3984 // there has been more UI text input, ignore it. We will receive 3985 // another update when that text is recognized. 3986 if (text != null && !text.equals(mWebTextView.getText().toString()) 3987 && nativeTextGeneration() == mTextGeneration) { 3988 mWebTextView.setTextAndKeepSelection(text); 3989 } 3990 } else { 3991 mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ? 3992 Gravity.RIGHT : Gravity.NO_GRAVITY); 3993 // This needs to be called before setType, which may call 3994 // requestFormData, and it needs to have the correct nodePointer. 3995 mWebTextView.setNodePointer(nodePointer); 3996 mWebTextView.setType(nativeFocusCandidateType()); 3997 Rect paddingRect = nativeFocusCandidatePaddingRect(); 3998 if (paddingRect != null) { 3999 // Use contentToViewDimension since these are the dimensions of 4000 // the padding. 4001 mWebTextView.setPadding( 4002 contentToViewDimension(paddingRect.left), 4003 contentToViewDimension(paddingRect.top), 4004 contentToViewDimension(paddingRect.right), 4005 contentToViewDimension(paddingRect.bottom)); 4006 } 4007 if (null == text) { 4008 if (DebugFlags.WEB_VIEW) { 4009 Log.v(LOGTAG, "rebuildWebTextView null == text"); 4010 } 4011 text = ""; 4012 } 4013 mWebTextView.setTextAndKeepSelection(text); 4014 InputMethodManager imm = InputMethodManager.peekInstance(); 4015 if (imm != null && imm.isActive(mWebTextView)) { 4016 imm.restartInput(mWebTextView); 4017 } 4018 } 4019 mWebTextView.requestFocus(); 4020 } 4021 4022 /** 4023 * Called by WebTextView to find saved form data associated with the 4024 * textfield 4025 * @param name Name of the textfield. 4026 * @param nodePointer Pointer to the node of the textfield, so it can be 4027 * compared to the currently focused textfield when the data is 4028 * retrieved. 4029 */ 4030 /* package */ void requestFormData(String name, int nodePointer) { 4031 if (mWebViewCore.getSettings().getSaveFormData()) { 4032 Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA); 4033 update.arg1 = nodePointer; 4034 RequestFormData updater = new RequestFormData(name, getUrl(), 4035 update); 4036 Thread t = new Thread(updater); 4037 t.start(); 4038 } 4039 } 4040 4041 /** 4042 * Pass a message to find out the <label> associated with the <input> 4043 * identified by nodePointer 4044 * @param framePointer Pointer to the frame containing the <input> node 4045 * @param nodePointer Pointer to the node for which a <label> is desired. 4046 */ 4047 /* package */ void requestLabel(int framePointer, int nodePointer) { 4048 mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer, 4049 nodePointer); 4050 } 4051 4052 /* 4053 * This class requests an Adapter for the WebTextView which shows past 4054 * entries stored in the database. It is a Runnable so that it can be done 4055 * in its own thread, without slowing down the UI. 4056 */ 4057 private class RequestFormData implements Runnable { 4058 private String mName; 4059 private String mUrl; 4060 private Message mUpdateMessage; 4061 4062 public RequestFormData(String name, String url, Message msg) { 4063 mName = name; 4064 mUrl = url; 4065 mUpdateMessage = msg; 4066 } 4067 4068 public void run() { 4069 ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName); 4070 if (pastEntries.size() > 0) { 4071 AutoCompleteAdapter adapter = new 4072 AutoCompleteAdapter(mContext, pastEntries); 4073 mUpdateMessage.obj = adapter; 4074 mUpdateMessage.sendToTarget(); 4075 } 4076 } 4077 } 4078 4079 /** 4080 * Dump the display tree to "/sdcard/displayTree.txt" 4081 * 4082 * @hide debug only 4083 */ 4084 public void dumpDisplayTree() { 4085 nativeDumpDisplayTree(getUrl()); 4086 } 4087 4088 /** 4089 * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to 4090 * "/sdcard/domTree.txt" 4091 * 4092 * @hide debug only 4093 */ 4094 public void dumpDomTree(boolean toFile) { 4095 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0); 4096 } 4097 4098 /** 4099 * Dump the render tree to adb shell if "toFile" is False, otherwise dump it 4100 * to "/sdcard/renderTree.txt" 4101 * 4102 * @hide debug only 4103 */ 4104 public void dumpRenderTree(boolean toFile) { 4105 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0); 4106 } 4107 4108 /** 4109 * Dump the V8 counters to standard output. 4110 * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to 4111 * true. Otherwise, this will do nothing. 4112 * 4113 * @hide debug only 4114 */ 4115 public void dumpV8Counters() { 4116 mWebViewCore.sendMessage(EventHub.DUMP_V8COUNTERS); 4117 } 4118 4119 // This is used to determine long press with the center key. Does not 4120 // affect long press with the trackball/touch. 4121 private boolean mGotCenterDown = false; 4122 4123 @Override 4124 public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { 4125 // send complex characters to webkit for use by JS and plugins 4126 if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) { 4127 // pass the key to DOM 4128 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 4129 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 4130 // return true as DOM handles the key 4131 return true; 4132 } 4133 return false; 4134 } 4135 4136 @Override 4137 public boolean onKeyDown(int keyCode, KeyEvent event) { 4138 if (DebugFlags.WEB_VIEW) { 4139 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() 4140 + ", " + event + ", unicode=" + event.getUnicodeChar()); 4141 } 4142 4143 if (mNativeClass == 0) { 4144 return false; 4145 } 4146 4147 // do this hack up front, so it always works, regardless of touch-mode 4148 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) { 4149 mAutoRedraw = !mAutoRedraw; 4150 if (mAutoRedraw) { 4151 invalidate(); 4152 } 4153 return true; 4154 } 4155 4156 // Bubble up the key event if 4157 // 1. it is a system key; or 4158 // 2. the host application wants to handle it; 4159 if (event.isSystem() 4160 || mCallbackProxy.uiOverrideKeyEvent(event)) { 4161 return false; 4162 } 4163 4164 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 4165 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 4166 if (nativeFocusIsPlugin()) { 4167 mShiftIsPressed = true; 4168 } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) { 4169 setUpSelect(); 4170 } 4171 } 4172 4173 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 4174 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 4175 switchOutDrawHistory(); 4176 if (nativeFocusIsPlugin()) { 4177 letPluginHandleNavKey(keyCode, event.getEventTime(), true); 4178 return true; 4179 } 4180 if (mSelectingText) { 4181 int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT 4182 ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0; 4183 int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ? 4184 -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0; 4185 int multiplier = event.getRepeatCount() + 1; 4186 moveSelection(xRate * multiplier, yRate * multiplier); 4187 return true; 4188 } 4189 if (navHandledKey(keyCode, 1, false, event.getEventTime())) { 4190 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 4191 return true; 4192 } 4193 // Bubble up the key event as WebView doesn't handle it 4194 return false; 4195 } 4196 4197 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 4198 switchOutDrawHistory(); 4199 if (event.getRepeatCount() == 0) { 4200 if (mSelectingText) { 4201 return true; // discard press if copy in progress 4202 } 4203 mGotCenterDown = true; 4204 mPrivateHandler.sendMessageDelayed(mPrivateHandler 4205 .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); 4206 // Already checked mNativeClass, so we do not need to check it 4207 // again. 4208 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 4209 return true; 4210 } 4211 // Bubble up the key event as WebView doesn't handle it 4212 return false; 4213 } 4214 4215 if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT 4216 && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) { 4217 // turn off copy select if a shift-key combo is pressed 4218 selectionDone(); 4219 mShiftIsPressed = false; 4220 } 4221 4222 if (getSettings().getNavDump()) { 4223 switch (keyCode) { 4224 case KeyEvent.KEYCODE_4: 4225 dumpDisplayTree(); 4226 break; 4227 case KeyEvent.KEYCODE_5: 4228 case KeyEvent.KEYCODE_6: 4229 dumpDomTree(keyCode == KeyEvent.KEYCODE_5); 4230 break; 4231 case KeyEvent.KEYCODE_7: 4232 case KeyEvent.KEYCODE_8: 4233 dumpRenderTree(keyCode == KeyEvent.KEYCODE_7); 4234 break; 4235 case KeyEvent.KEYCODE_9: 4236 nativeInstrumentReport(); 4237 return true; 4238 } 4239 } 4240 4241 if (nativeCursorIsTextInput()) { 4242 // This message will put the node in focus, for the DOM's notion 4243 // of focus, and make the focuscontroller active 4244 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 4245 nativeCursorNodePointer()); 4246 // This will bring up the WebTextView and put it in focus, for 4247 // our view system's notion of focus 4248 rebuildWebTextView(); 4249 // Now we need to pass the event to it 4250 if (inEditingMode()) { 4251 mWebTextView.setDefaultSelection(); 4252 return mWebTextView.dispatchKeyEvent(event); 4253 } 4254 } else if (nativeHasFocusNode()) { 4255 // In this case, the cursor is not on a text input, but the focus 4256 // might be. Check it, and if so, hand over to the WebTextView. 4257 rebuildWebTextView(); 4258 if (inEditingMode()) { 4259 mWebTextView.setDefaultSelection(); 4260 return mWebTextView.dispatchKeyEvent(event); 4261 } 4262 } 4263 4264 // TODO: should we pass all the keys to DOM or check the meta tag 4265 if (nativeCursorWantsKeyEvents() || true) { 4266 // pass the key to DOM 4267 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 4268 // return true as DOM handles the key 4269 return true; 4270 } 4271 4272 // Bubble up the key event as WebView doesn't handle it 4273 return false; 4274 } 4275 4276 @Override 4277 public boolean onKeyUp(int keyCode, KeyEvent event) { 4278 if (DebugFlags.WEB_VIEW) { 4279 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() 4280 + ", " + event + ", unicode=" + event.getUnicodeChar()); 4281 } 4282 4283 if (mNativeClass == 0) { 4284 return false; 4285 } 4286 4287 // special CALL handling when cursor node's href is "tel:XXX" 4288 if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) { 4289 String text = nativeCursorText(); 4290 if (!nativeCursorIsTextInput() && text != null 4291 && text.startsWith(SCHEME_TEL)) { 4292 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); 4293 getContext().startActivity(intent); 4294 return true; 4295 } 4296 } 4297 4298 // Bubble up the key event if 4299 // 1. it is a system key; or 4300 // 2. the host application wants to handle it; 4301 if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) { 4302 return false; 4303 } 4304 4305 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 4306 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 4307 if (nativeFocusIsPlugin()) { 4308 mShiftIsPressed = false; 4309 } else if (copySelection()) { 4310 selectionDone(); 4311 return true; 4312 } 4313 } 4314 4315 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 4316 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 4317 if (nativeFocusIsPlugin()) { 4318 letPluginHandleNavKey(keyCode, event.getEventTime(), false); 4319 return true; 4320 } 4321 // always handle the navigation keys in the UI thread 4322 // Bubble up the key event as WebView doesn't handle it 4323 return false; 4324 } 4325 4326 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 4327 // remove the long press message first 4328 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 4329 mGotCenterDown = false; 4330 4331 if (mSelectingText) { 4332 if (mExtendSelection) { 4333 copySelection(); 4334 selectionDone(); 4335 } else { 4336 mExtendSelection = true; 4337 nativeSetExtendSelection(); 4338 invalidate(); // draw the i-beam instead of the arrow 4339 } 4340 return true; // discard press if copy in progress 4341 } 4342 4343 // perform the single click 4344 Rect visibleRect = sendOurVisibleRect(); 4345 // Note that sendOurVisibleRect calls viewToContent, so the 4346 // coordinates should be in content coordinates. 4347 if (!nativeCursorIntersects(visibleRect)) { 4348 return false; 4349 } 4350 WebViewCore.CursorData data = cursorData(); 4351 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); 4352 playSoundEffect(SoundEffectConstants.CLICK); 4353 if (nativeCursorIsTextInput()) { 4354 rebuildWebTextView(); 4355 centerKeyPressOnTextField(); 4356 if (inEditingMode()) { 4357 mWebTextView.setDefaultSelection(); 4358 } 4359 return true; 4360 } 4361 clearTextEntry(true); 4362 nativeSetFollowedLink(true); 4363 if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) { 4364 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, 4365 nativeCursorNodePointer()); 4366 } 4367 return true; 4368 } 4369 4370 // TODO: should we pass all the keys to DOM or check the meta tag 4371 if (nativeCursorWantsKeyEvents() || true) { 4372 // pass the key to DOM 4373 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 4374 // return true as DOM handles the key 4375 return true; 4376 } 4377 4378 // Bubble up the key event as WebView doesn't handle it 4379 return false; 4380 } 4381 4382 /** 4383 * @hide pending API council approval. 4384 */ 4385 public void setUpSelect() { 4386 if (0 == mNativeClass) return; // client isn't initialized 4387 if (inFullScreenMode()) return; 4388 if (mSelectingText) return; 4389 mExtendSelection = false; 4390 mSelectingText = mDrawSelectionPointer = true; 4391 // don't let the picture change during text selection 4392 WebViewCore.pauseUpdatePicture(mWebViewCore); 4393 nativeResetSelection(); 4394 if (nativeHasCursorNode()) { 4395 Rect rect = nativeCursorNodeBounds(); 4396 mSelectX = contentToViewX(rect.left); 4397 mSelectY = contentToViewY(rect.top); 4398 } else if (mLastTouchY > getVisibleTitleHeight()) { 4399 mSelectX = mScrollX + (int) mLastTouchX; 4400 mSelectY = mScrollY + (int) mLastTouchY; 4401 } else { 4402 mSelectX = mScrollX + getViewWidth() / 2; 4403 mSelectY = mScrollY + getViewHeightWithTitle() / 2; 4404 } 4405 nativeHideCursor(); 4406 } 4407 4408 /** 4409 * Use this method to put the WebView into text selection mode. 4410 * Do not rely on this functionality; it will be deprecated in the future. 4411 */ 4412 public void emulateShiftHeld() { 4413 setUpSelect(); 4414 } 4415 4416 /** 4417 * @hide pending API council approval. 4418 */ 4419 public void selectAll() { 4420 if (0 == mNativeClass) return; // client isn't initialized 4421 if (inFullScreenMode()) return; 4422 if (!mSelectingText) setUpSelect(); 4423 nativeSelectAll(); 4424 mDrawSelectionPointer = false; 4425 mExtendSelection = true; 4426 invalidate(); 4427 } 4428 4429 /** 4430 * @hide pending API council approval. 4431 */ 4432 public boolean selectDialogIsUp() { 4433 return mSelectingText; 4434 } 4435 4436 /** 4437 * @hide pending API council approval. 4438 */ 4439 public void notifySelectDialogDismissed() { 4440 mSelectingText = false; 4441 WebViewCore.resumeUpdatePicture(mWebViewCore); 4442 } 4443 4444 /** 4445 * @hide pending API council approval. 4446 */ 4447 public void selectionDone() { 4448 if (mSelectingText) { 4449 WebChromeClient client = getWebChromeClient(); 4450 if (client != null) client.onSelectionDone(this); 4451 invalidate(); // redraw without selection 4452 notifySelectDialogDismissed(); 4453 } 4454 } 4455 4456 /** 4457 * @hide pending API council approval. 4458 */ 4459 public boolean copySelection() { 4460 boolean copiedSomething = false; 4461 String selection = getSelection(); 4462 if (selection != "") { 4463 if (DebugFlags.WEB_VIEW) { 4464 Log.v(LOGTAG, "copySelection \"" + selection + "\""); 4465 } 4466 Toast.makeText(mContext 4467 , com.android.internal.R.string.text_copied 4468 , Toast.LENGTH_SHORT).show(); 4469 copiedSomething = true; 4470 try { 4471 IClipboard clip = IClipboard.Stub.asInterface( 4472 ServiceManager.getService("clipboard")); 4473 clip.setClipboardText(selection); 4474 } catch (android.os.RemoteException e) { 4475 Log.e(LOGTAG, "Clipboard failed", e); 4476 } 4477 } 4478 invalidate(); // remove selection region and pointer 4479 return copiedSomething; 4480 } 4481 4482 /** 4483 * @hide pending API council approval. 4484 */ 4485 public String getSelection() { 4486 if (mNativeClass == 0) return ""; 4487 return nativeGetSelection(); 4488 } 4489 4490 @Override 4491 protected void onAttachedToWindow() { 4492 super.onAttachedToWindow(); 4493 if (hasWindowFocus()) setActive(true); 4494 } 4495 4496 @Override 4497 protected void onDetachedFromWindow() { 4498 clearHelpers(); 4499 dismissZoomControl(); 4500 if (hasWindowFocus()) setActive(false); 4501 super.onDetachedFromWindow(); 4502 } 4503 4504 /** 4505 * @deprecated WebView no longer needs to implement 4506 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 4507 */ 4508 @Deprecated 4509 public void onChildViewAdded(View parent, View child) {} 4510 4511 /** 4512 * @deprecated WebView no longer needs to implement 4513 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 4514 */ 4515 @Deprecated 4516 public void onChildViewRemoved(View p, View child) {} 4517 4518 /** 4519 * @deprecated WebView should not have implemented 4520 * ViewTreeObserver.OnGlobalFocusChangeListener. This method 4521 * does nothing now. 4522 */ 4523 @Deprecated 4524 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 4525 } 4526 4527 private void setActive(boolean active) { 4528 if (active) { 4529 if (hasFocus()) { 4530 // If our window regained focus, and we have focus, then begin 4531 // drawing the cursor ring 4532 mDrawCursorRing = true; 4533 if (mNativeClass != 0) { 4534 nativeRecordButtons(true, false, true); 4535 if (inEditingMode()) { 4536 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0); 4537 } 4538 } 4539 } else { 4540 // If our window gained focus, but we do not have it, do not 4541 // draw the cursor ring. 4542 mDrawCursorRing = false; 4543 // We do not call nativeRecordButtons here because we assume 4544 // that when we lost focus, or window focus, it got called with 4545 // false for the first parameter 4546 } 4547 } else { 4548 if (mWebViewCore != null && getSettings().getBuiltInZoomControls() 4549 && (mZoomButtonsController == null || 4550 !mZoomButtonsController.isVisible())) { 4551 /* 4552 * The zoom controls come in their own window, so our window 4553 * loses focus. Our policy is to not draw the cursor ring if 4554 * our window is not focused, but this is an exception since 4555 * the user can still navigate the web page with the zoom 4556 * controls showing. 4557 */ 4558 // If our window has lost focus, stop drawing the cursor ring 4559 mDrawCursorRing = false; 4560 } 4561 mGotKeyDown = false; 4562 mShiftIsPressed = false; 4563 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 4564 mTouchMode = TOUCH_DONE_MODE; 4565 if (mNativeClass != 0) { 4566 nativeRecordButtons(false, false, true); 4567 } 4568 setFocusControllerInactive(); 4569 } 4570 invalidate(); 4571 } 4572 4573 // To avoid drawing the cursor ring, and remove the TextView when our window 4574 // loses focus. 4575 @Override 4576 public void onWindowFocusChanged(boolean hasWindowFocus) { 4577 setActive(hasWindowFocus); 4578 if (hasWindowFocus) { 4579 JWebCoreJavaBridge.setActiveWebView(this); 4580 } else { 4581 JWebCoreJavaBridge.removeActiveWebView(this); 4582 } 4583 super.onWindowFocusChanged(hasWindowFocus); 4584 } 4585 4586 /* 4587 * Pass a message to WebCore Thread, telling the WebCore::Page's 4588 * FocusController to be "inactive" so that it will 4589 * not draw the blinking cursor. It gets set to "active" to draw the cursor 4590 * in WebViewCore.cpp, when the WebCore thread receives key events/clicks. 4591 */ 4592 /* package */ void setFocusControllerInactive() { 4593 // Do not need to also check whether mWebViewCore is null, because 4594 // mNativeClass is only set if mWebViewCore is non null 4595 if (mNativeClass == 0) return; 4596 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0); 4597 } 4598 4599 @Override 4600 protected void onFocusChanged(boolean focused, int direction, 4601 Rect previouslyFocusedRect) { 4602 if (DebugFlags.WEB_VIEW) { 4603 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); 4604 } 4605 if (focused) { 4606 // When we regain focus, if we have window focus, resume drawing 4607 // the cursor ring 4608 if (hasWindowFocus()) { 4609 mDrawCursorRing = true; 4610 if (mNativeClass != 0) { 4611 nativeRecordButtons(true, false, true); 4612 } 4613 //} else { 4614 // The WebView has gained focus while we do not have 4615 // windowfocus. When our window lost focus, we should have 4616 // called nativeRecordButtons(false...) 4617 } 4618 } else { 4619 // When we lost focus, unless focus went to the TextView (which is 4620 // true if we are in editing mode), stop drawing the cursor ring. 4621 if (!inEditingMode()) { 4622 mDrawCursorRing = false; 4623 if (mNativeClass != 0) { 4624 nativeRecordButtons(false, false, true); 4625 } 4626 setFocusControllerInactive(); 4627 } 4628 mGotKeyDown = false; 4629 } 4630 4631 super.onFocusChanged(focused, direction, previouslyFocusedRect); 4632 } 4633 4634 /** 4635 * @hide 4636 */ 4637 @Override 4638 protected boolean setFrame(int left, int top, int right, int bottom) { 4639 boolean changed = super.setFrame(left, top, right, bottom); 4640 if (!changed && mHeightCanMeasure) { 4641 // When mHeightCanMeasure is true, we will set mLastHeightSent to 0 4642 // in WebViewCore after we get the first layout. We do call 4643 // requestLayout() when we get contentSizeChanged(). But the View 4644 // system won't call onSizeChanged if the dimension is not changed. 4645 // In this case, we need to call sendViewSizeZoom() explicitly to 4646 // notify the WebKit about the new dimensions. 4647 sendViewSizeZoom(); 4648 } 4649 return changed; 4650 } 4651 4652 private static class PostScale implements Runnable { 4653 final WebView mWebView; 4654 final boolean mUpdateTextWrap; 4655 4656 public PostScale(WebView webView, boolean updateTextWrap) { 4657 mWebView = webView; 4658 mUpdateTextWrap = updateTextWrap; 4659 } 4660 4661 public void run() { 4662 if (mWebView.mWebViewCore != null) { 4663 // we always force, in case our height changed, in which case we 4664 // still want to send the notification over to webkit. 4665 mWebView.setNewZoomScale(mWebView.mActualScale, 4666 mUpdateTextWrap, true); 4667 // update the zoom buttons as the scale can be changed 4668 if (mWebView.getSettings().getBuiltInZoomControls()) { 4669 mWebView.updateZoomButtonsEnabled(); 4670 } 4671 } 4672 } 4673 } 4674 4675 @Override 4676 protected void onSizeChanged(int w, int h, int ow, int oh) { 4677 super.onSizeChanged(w, h, ow, oh); 4678 // Center zooming to the center of the screen. 4679 if (mZoomScale == 0) { // unless we're already zooming 4680 // To anchor at top left corner. 4681 mZoomCenterX = 0; 4682 mZoomCenterY = getVisibleTitleHeight(); 4683 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 4684 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 4685 } 4686 4687 // adjust the max viewport width depending on the view dimensions. This 4688 // is to ensure the scaling is not going insane. So do not shrink it if 4689 // the view size is temporarily smaller, e.g. when soft keyboard is up. 4690 int newMaxViewportWidth = (int) (Math.max(w, h) / DEFAULT_MIN_ZOOM_SCALE); 4691 if (newMaxViewportWidth > sMaxViewportWidth) { 4692 sMaxViewportWidth = newMaxViewportWidth; 4693 } 4694 4695 // update mMinZoomScale if the minimum zoom scale is not fixed 4696 if (!mMinZoomScaleFixed) { 4697 // when change from narrow screen to wide screen, the new viewWidth 4698 // can be wider than the old content width. We limit the minimum 4699 // scale to 1.0f. The proper minimum scale will be calculated when 4700 // the new picture shows up. 4701 mMinZoomScale = Math.min(1.0f, (float) getViewWidth() 4702 / (mDrawHistory ? mHistoryPicture.getWidth() 4703 : mZoomOverviewWidth)); 4704 if (mInitialScaleInPercent > 0) { 4705 // limit the minZoomScale to the initialScale if it is set 4706 float initialScale = mInitialScaleInPercent / 100.0f; 4707 if (mMinZoomScale > initialScale) { 4708 mMinZoomScale = initialScale; 4709 } 4710 } 4711 } 4712 4713 dismissZoomControl(); 4714 4715 // onSizeChanged() is called during WebView layout. And any 4716 // requestLayout() is blocked during layout. As setNewZoomScale() will 4717 // call its child View to reposition itself through ViewManager's 4718 // scaleAll(), we need to post a Runnable to ensure requestLayout(). 4719 // <b/> 4720 // only update the text wrap scale if width changed. 4721 post(new PostScale(this, w != ow)); 4722 } 4723 4724 @Override 4725 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 4726 super.onScrollChanged(l, t, oldl, oldt); 4727 if (!mInOverScrollMode) { 4728 sendOurVisibleRect(); 4729 // update WebKit if visible title bar height changed. The logic is same 4730 // as getVisibleTitleHeight. 4731 int titleHeight = getTitleHeight(); 4732 if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { 4733 sendViewSizeZoom(); 4734 } 4735 } 4736 } 4737 4738 @Override 4739 public boolean dispatchKeyEvent(KeyEvent event) { 4740 boolean dispatch = true; 4741 4742 // Textfields and plugins need to receive the shift up key even if 4743 // another key was released while the shift key was held down. 4744 if (!inEditingMode() && (mNativeClass == 0 || !nativeFocusIsPlugin())) { 4745 if (event.getAction() == KeyEvent.ACTION_DOWN) { 4746 mGotKeyDown = true; 4747 } else { 4748 if (!mGotKeyDown) { 4749 /* 4750 * We got a key up for which we were not the recipient of 4751 * the original key down. Don't give it to the view. 4752 */ 4753 dispatch = false; 4754 } 4755 mGotKeyDown = false; 4756 } 4757 } 4758 4759 if (dispatch) { 4760 return super.dispatchKeyEvent(event); 4761 } else { 4762 // We didn't dispatch, so let something else handle the key 4763 return false; 4764 } 4765 } 4766 4767 // Here are the snap align logic: 4768 // 1. If it starts nearly horizontally or vertically, snap align; 4769 // 2. If there is a dramitic direction change, let it go; 4770 // 3. If there is a same direction back and forth, lock it. 4771 4772 // adjustable parameters 4773 private int mMinLockSnapReverseDistance; 4774 private static final float MAX_SLOPE_FOR_DIAG = 1.5f; 4775 private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80; 4776 4777 private static int sign(float x) { 4778 return x > 0 ? 1 : (x < 0 ? -1 : 0); 4779 } 4780 4781 // if the page can scroll <= this value, we won't allow the drag tracker 4782 // to have any effect. 4783 private static final int MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER = 4; 4784 4785 private class DragTrackerHandler { 4786 private final DragTracker mProxy; 4787 private final float mStartY, mStartX; 4788 private final float mMinDY, mMinDX; 4789 private final float mMaxDY, mMaxDX; 4790 private float mCurrStretchY, mCurrStretchX; 4791 private int mSX, mSY; 4792 private Interpolator mInterp; 4793 private float[] mXY = new float[2]; 4794 4795 // inner (non-state) classes can't have enums :( 4796 private static final int DRAGGING_STATE = 0; 4797 private static final int ANIMATING_STATE = 1; 4798 private static final int FINISHED_STATE = 2; 4799 private int mState; 4800 4801 public DragTrackerHandler(float x, float y, DragTracker proxy) { 4802 mProxy = proxy; 4803 4804 int docBottom = computeRealVerticalScrollRange() + getTitleHeight(); 4805 int viewTop = getScrollY(); 4806 int viewBottom = viewTop + getHeight(); 4807 4808 mStartY = y; 4809 mMinDY = -viewTop; 4810 mMaxDY = docBottom - viewBottom; 4811 4812 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4813 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " dragtracker y= " + y + 4814 " up/down= " + mMinDY + " " + mMaxDY); 4815 } 4816 4817 int docRight = computeRealHorizontalScrollRange(); 4818 int viewLeft = getScrollX(); 4819 int viewRight = viewLeft + getWidth(); 4820 mStartX = x; 4821 mMinDX = -viewLeft; 4822 mMaxDX = docRight - viewRight; 4823 4824 mState = DRAGGING_STATE; 4825 mProxy.onStartDrag(x, y); 4826 4827 // ensure we buildBitmap at least once 4828 mSX = -99999; 4829 } 4830 4831 private float computeStretch(float delta, float min, float max) { 4832 float stretch = 0; 4833 if (max - min > MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER) { 4834 if (delta < min) { 4835 stretch = delta - min; 4836 } else if (delta > max) { 4837 stretch = delta - max; 4838 } 4839 } 4840 return stretch; 4841 } 4842 4843 public void dragTo(float x, float y) { 4844 float sy = computeStretch(mStartY - y, mMinDY, mMaxDY); 4845 float sx = computeStretch(mStartX - x, mMinDX, mMaxDX); 4846 4847 if ((mSnapScrollMode & SNAP_X) != 0) { 4848 sy = 0; 4849 } else if ((mSnapScrollMode & SNAP_Y) != 0) { 4850 sx = 0; 4851 } 4852 4853 if (mCurrStretchX != sx || mCurrStretchY != sy) { 4854 mCurrStretchX = sx; 4855 mCurrStretchY = sy; 4856 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4857 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "---- stretch " + sx + 4858 " " + sy); 4859 } 4860 if (mProxy.onStretchChange(sx, sy)) { 4861 invalidate(); 4862 } 4863 } 4864 } 4865 4866 public void stopDrag() { 4867 final int DURATION = 200; 4868 int now = (int)SystemClock.uptimeMillis(); 4869 mInterp = new Interpolator(2); 4870 mXY[0] = mCurrStretchX; 4871 mXY[1] = mCurrStretchY; 4872 // float[] blend = new float[] { 0.5f, 0, 0.75f, 1 }; 4873 float[] blend = new float[] { 0, 0.5f, 0.75f, 1 }; 4874 mInterp.setKeyFrame(0, now, mXY, blend); 4875 float[] zerozero = new float[] { 0, 0 }; 4876 mInterp.setKeyFrame(1, now + DURATION, zerozero, null); 4877 mState = ANIMATING_STATE; 4878 4879 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4880 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag, starting animation"); 4881 } 4882 } 4883 4884 // Call this after each draw. If it ruturns null, the tracker is done 4885 public boolean isFinished() { 4886 return mState == FINISHED_STATE; 4887 } 4888 4889 private int hiddenHeightOfTitleBar() { 4890 return getTitleHeight() - getVisibleTitleHeight(); 4891 } 4892 4893 // need a way to know if 565 or 8888 is the right config for 4894 // capturing the display and giving it to the drag proxy 4895 private Bitmap.Config offscreenBitmapConfig() { 4896 // hard code 565 for now 4897 return Bitmap.Config.RGB_565; 4898 } 4899 4900 /* If the tracker draws, then this returns true, otherwise it will 4901 return false, and draw nothing. 4902 */ 4903 public boolean draw(Canvas canvas) { 4904 if (mCurrStretchX != 0 || mCurrStretchY != 0) { 4905 int sx = getScrollX(); 4906 int sy = getScrollY() - hiddenHeightOfTitleBar(); 4907 if (mSX != sx || mSY != sy) { 4908 buildBitmap(sx, sy); 4909 mSX = sx; 4910 mSY = sy; 4911 } 4912 4913 if (mState == ANIMATING_STATE) { 4914 Interpolator.Result result = mInterp.timeToValues(mXY); 4915 if (result == Interpolator.Result.FREEZE_END) { 4916 mState = FINISHED_STATE; 4917 return false; 4918 } else { 4919 mProxy.onStretchChange(mXY[0], mXY[1]); 4920 invalidate(); 4921 // fall through to the draw 4922 } 4923 } 4924 int count = canvas.save(Canvas.MATRIX_SAVE_FLAG); 4925 canvas.translate(sx, sy); 4926 mProxy.onDraw(canvas); 4927 canvas.restoreToCount(count); 4928 return true; 4929 } 4930 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4931 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " -- draw false " + 4932 mCurrStretchX + " " + mCurrStretchY); 4933 } 4934 return false; 4935 } 4936 4937 private void buildBitmap(int sx, int sy) { 4938 int w = getWidth(); 4939 int h = getViewHeight(); 4940 Bitmap bm = Bitmap.createBitmap(w, h, offscreenBitmapConfig()); 4941 Canvas canvas = new Canvas(bm); 4942 canvas.translate(-sx, -sy); 4943 drawContent(canvas); 4944 4945 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4946 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "--- buildBitmap " + sx + 4947 " " + sy + " " + w + " " + h); 4948 } 4949 mProxy.onBitmapChange(bm); 4950 } 4951 } 4952 4953 /** @hide */ 4954 public static class DragTracker { 4955 public void onStartDrag(float x, float y) {} 4956 public boolean onStretchChange(float sx, float sy) { 4957 // return true to have us inval the view 4958 return false; 4959 } 4960 public void onStopDrag() {} 4961 public void onBitmapChange(Bitmap bm) {} 4962 public void onDraw(Canvas canvas) {} 4963 } 4964 4965 /** @hide */ 4966 public DragTracker getDragTracker() { 4967 return mDragTracker; 4968 } 4969 4970 /** @hide */ 4971 public void setDragTracker(DragTracker tracker) { 4972 mDragTracker = tracker; 4973 } 4974 4975 private DragTracker mDragTracker; 4976 private DragTrackerHandler mDragTrackerHandler; 4977 4978 private class ScaleDetectorListener implements 4979 ScaleGestureDetector.OnScaleGestureListener { 4980 4981 public boolean onScaleBegin(ScaleGestureDetector detector) { 4982 // cancel the single touch handling 4983 cancelTouch(); 4984 dismissZoomControl(); 4985 // reset the zoom overview mode so that the page won't auto grow 4986 mInZoomOverview = false; 4987 // If it is in password mode, turn it off so it does not draw 4988 // misplaced. 4989 if (inEditingMode() && nativeFocusCandidateIsPassword()) { 4990 mWebTextView.setInPassword(false); 4991 } 4992 4993 mViewManager.startZoom(); 4994 4995 return true; 4996 } 4997 4998 public void onScaleEnd(ScaleGestureDetector detector) { 4999 if (mPreviewZoomOnly) { 5000 mPreviewZoomOnly = false; 5001 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 5002 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 5003 // don't reflow when zoom in; when zoom out, do reflow if the 5004 // new scale is almost minimum scale; 5005 boolean reflowNow = (mActualScale - mMinZoomScale 5006 <= MINIMUM_SCALE_INCREMENT) 5007 || ((mActualScale <= 0.8 * mTextWrapScale)); 5008 // force zoom after mPreviewZoomOnly is set to false so that the 5009 // new view size will be passed to the WebKit 5010 setNewZoomScale(mActualScale, reflowNow, true); 5011 // call invalidate() to draw without zoom filter 5012 invalidate(); 5013 } 5014 // adjust the edit text view if needed 5015 if (inEditingMode() && didUpdateTextViewBounds(false) 5016 && nativeFocusCandidateIsPassword()) { 5017 // If it is a password field, start drawing the 5018 // WebTextView once again. 5019 mWebTextView.setInPassword(true); 5020 } 5021 // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it 5022 // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it 5023 // may trigger the unwanted fling. 5024 mTouchMode = TOUCH_PINCH_DRAG; 5025 mConfirmMove = true; 5026 startTouch(detector.getFocusX(), detector.getFocusY(), 5027 mLastTouchTime); 5028 5029 mViewManager.endZoom(); 5030 } 5031 5032 public boolean onScale(ScaleGestureDetector detector) { 5033 float scale = (float) (Math.round(detector.getScaleFactor() 5034 * mActualScale * 100) / 100.0); 5035 if (Math.abs(scale - mActualScale) >= MINIMUM_SCALE_INCREMENT) { 5036 mPreviewZoomOnly = true; 5037 // limit the scale change per step 5038 if (scale > mActualScale) { 5039 scale = Math.min(scale, mActualScale * 1.25f); 5040 } else { 5041 scale = Math.max(scale, mActualScale * 0.8f); 5042 } 5043 mZoomCenterX = detector.getFocusX(); 5044 mZoomCenterY = detector.getFocusY(); 5045 setNewZoomScale(scale, false, false); 5046 invalidate(); 5047 return true; 5048 } 5049 return false; 5050 } 5051 } 5052 5053 private boolean hitFocusedPlugin(int contentX, int contentY) { 5054 if (DebugFlags.WEB_VIEW) { 5055 Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin()); 5056 Rect r = nativeFocusNodeBounds(); 5057 Log.v(LOGTAG, "nativeFocusNodeBounds()=(" + r.left + ", " + r.top 5058 + ", " + r.right + ", " + r.bottom + ")"); 5059 } 5060 return nativeFocusIsPlugin() 5061 && nativeFocusNodeBounds().contains(contentX, contentY); 5062 } 5063 5064 private boolean shouldForwardTouchEvent() { 5065 return mFullScreenHolder != null || (mForwardTouchEvents 5066 && !mSelectingText 5067 && mPreventDefault != PREVENT_DEFAULT_IGNORE); 5068 } 5069 5070 private boolean inFullScreenMode() { 5071 return mFullScreenHolder != null; 5072 } 5073 5074 @Override 5075 public boolean onTouchEvent(MotionEvent ev) { 5076 if (mNativeClass == 0 || (!isClickable() && !isLongClickable())) { 5077 return false; 5078 } 5079 5080 if (DebugFlags.WEB_VIEW) { 5081 Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" 5082 + mTouchMode); 5083 } 5084 5085 int action; 5086 float x, y; 5087 long eventTime = ev.getEventTime(); 5088 5089 // FIXME: we may consider to give WebKit an option to handle multi-touch 5090 // events later. 5091 if (mSupportMultiTouch && ev.getPointerCount() > 1) { 5092 if (mAllowPanAndScale || mMinZoomScale < mMaxZoomScale) { 5093 mScaleDetector.onTouchEvent(ev); 5094 if (mScaleDetector.isInProgress()) { 5095 mLastTouchTime = eventTime; 5096 if (!mAllowPanAndScale) { 5097 return true; 5098 } 5099 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5100 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5101 } 5102 x = mScaleDetector.getFocusX(); 5103 y = mScaleDetector.getFocusY(); 5104 action = ev.getAction() & MotionEvent.ACTION_MASK; 5105 if (action == MotionEvent.ACTION_POINTER_DOWN) { 5106 cancelTouch(); 5107 action = MotionEvent.ACTION_DOWN; 5108 } else if (action == MotionEvent.ACTION_POINTER_UP) { 5109 // set mLastTouchX/Y to the remaining point 5110 mLastTouchX = x; 5111 mLastTouchY = y; 5112 } else if (action == MotionEvent.ACTION_MOVE) { 5113 // negative x or y indicate it is on the edge, skip it. 5114 if (x < 0 || y < 0) { 5115 return true; 5116 } 5117 } 5118 } else { 5119 // if the page disallow zoom, skip multi-pointer action 5120 return true; 5121 } 5122 } else { 5123 action = ev.getAction(); 5124 x = ev.getX(); 5125 y = ev.getY(); 5126 } 5127 5128 // Due to the touch screen edge effect, a touch closer to the edge 5129 // always snapped to the edge. As getViewWidth() can be different from 5130 // getWidth() due to the scrollbar, adjusting the point to match 5131 // getViewWidth(). Same applied to the height. 5132 if (x > getViewWidth() - 1) { 5133 x = getViewWidth() - 1; 5134 } 5135 if (y > getViewHeightWithTitle() - 1) { 5136 y = getViewHeightWithTitle() - 1; 5137 } 5138 5139 float fDeltaX = mLastTouchX - x; 5140 float fDeltaY = mLastTouchY - y; 5141 int deltaX = (int) fDeltaX; 5142 int deltaY = (int) fDeltaY; 5143 int contentX = viewToContentX((int) x + mScrollX); 5144 int contentY = viewToContentY((int) y + mScrollY); 5145 5146 switch (action) { 5147 case MotionEvent.ACTION_DOWN: { 5148 mPreventDefault = PREVENT_DEFAULT_NO; 5149 mConfirmMove = false; 5150 if (!mScroller.isFinished()) { 5151 // stop the current scroll animation, but if this is 5152 // the start of a fling, allow it to add to the current 5153 // fling's velocity 5154 mScroller.abortAnimation(); 5155 mTouchMode = TOUCH_DRAG_START_MODE; 5156 mConfirmMove = true; 5157 mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); 5158 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { 5159 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); 5160 if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { 5161 mTouchMode = TOUCH_DOUBLE_TAP_MODE; 5162 } else { 5163 // commit the short press action for the previous tap 5164 doShortPress(); 5165 mTouchMode = TOUCH_INIT_MODE; 5166 mDeferTouchProcess = (!inFullScreenMode() 5167 && mForwardTouchEvents) ? hitFocusedPlugin( 5168 contentX, contentY) : false; 5169 } 5170 } else { // the normal case 5171 mPreviewZoomOnly = false; 5172 mTouchMode = TOUCH_INIT_MODE; 5173 mDeferTouchProcess = (!inFullScreenMode() 5174 && mForwardTouchEvents) ? hitFocusedPlugin( 5175 contentX, contentY) : false; 5176 mWebViewCore.sendMessage( 5177 EventHub.UPDATE_FRAME_CACHE_IF_LOADING); 5178 if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { 5179 EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION, 5180 (eventTime - mLastTouchUpTime), eventTime); 5181 } 5182 if (mSelectingText) { 5183 mDrawSelectionPointer = false; 5184 mSelectionStarted = nativeStartSelection(contentX, contentY); 5185 if (DebugFlags.WEB_VIEW) { 5186 Log.v(LOGTAG, "select=" + contentX + "," + contentY); 5187 } 5188 invalidate(); 5189 } 5190 } 5191 // Trigger the link 5192 if (mTouchMode == TOUCH_INIT_MODE 5193 || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 5194 mPrivateHandler.sendEmptyMessageDelayed( 5195 SWITCH_TO_SHORTPRESS, TAP_TIMEOUT); 5196 mPrivateHandler.sendEmptyMessageDelayed( 5197 SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT); 5198 if (inFullScreenMode() || mDeferTouchProcess) { 5199 mPreventDefault = PREVENT_DEFAULT_YES; 5200 } else if (mForwardTouchEvents) { 5201 mPreventDefault = PREVENT_DEFAULT_MAYBE_YES; 5202 } else { 5203 mPreventDefault = PREVENT_DEFAULT_NO; 5204 } 5205 // pass the touch events from UI thread to WebCore thread 5206 if (shouldForwardTouchEvent()) { 5207 TouchEventData ted = new TouchEventData(); 5208 ted.mAction = action; 5209 ted.mX = contentX; 5210 ted.mY = contentY; 5211 ted.mMetaState = ev.getMetaState(); 5212 ted.mReprocess = mDeferTouchProcess; 5213 if (mDeferTouchProcess) { 5214 // still needs to set them for compute deltaX/Y 5215 mLastTouchX = x; 5216 mLastTouchY = y; 5217 ted.mViewX = x; 5218 ted.mViewY = y; 5219 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5220 break; 5221 } 5222 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5223 if (!inFullScreenMode()) { 5224 mPrivateHandler.sendMessageDelayed(mPrivateHandler 5225 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 5226 action, 0), TAP_TIMEOUT); 5227 } 5228 } 5229 } 5230 startTouch(x, y, eventTime); 5231 break; 5232 } 5233 case MotionEvent.ACTION_MOVE: { 5234 boolean firstMove = false; 5235 if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY) 5236 >= mTouchSlopSquare) { 5237 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5238 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5239 mConfirmMove = true; 5240 firstMove = true; 5241 if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 5242 mTouchMode = TOUCH_INIT_MODE; 5243 } 5244 } 5245 // pass the touch events from UI thread to WebCore thread 5246 if (shouldForwardTouchEvent() && mConfirmMove && (firstMove 5247 || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) { 5248 mLastSentTouchTime = eventTime; 5249 TouchEventData ted = new TouchEventData(); 5250 ted.mAction = action; 5251 ted.mX = contentX; 5252 ted.mY = contentY; 5253 ted.mMetaState = ev.getMetaState(); 5254 ted.mReprocess = mDeferTouchProcess; 5255 if (mDeferTouchProcess) { 5256 ted.mViewX = x; 5257 ted.mViewY = y; 5258 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5259 break; 5260 } 5261 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5262 if (firstMove && !inFullScreenMode()) { 5263 mPrivateHandler.sendMessageDelayed(mPrivateHandler 5264 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 5265 action, 0), TAP_TIMEOUT); 5266 } 5267 } 5268 if (mTouchMode == TOUCH_DONE_MODE 5269 || mPreventDefault == PREVENT_DEFAULT_YES) { 5270 // no dragging during scroll zoom animation, or when prevent 5271 // default is yes 5272 break; 5273 } 5274 if (mVelocityTracker == null) { 5275 Log.e(LOGTAG, "Got null mVelocityTracker when " 5276 + "mPreventDefault = " + mPreventDefault 5277 + " mDeferTouchProcess = " + mDeferTouchProcess 5278 + " mTouchMode = " + mTouchMode); 5279 } 5280 mVelocityTracker.addMovement(ev); 5281 if (mSelectingText && mSelectionStarted) { 5282 if (DebugFlags.WEB_VIEW) { 5283 Log.v(LOGTAG, "extend=" + contentX + "," + contentY); 5284 } 5285 nativeExtendSelection(contentX, contentY); 5286 invalidate(); 5287 break; 5288 } 5289 5290 if (mTouchMode != TOUCH_DRAG_MODE) { 5291 5292 if (!mConfirmMove) { 5293 break; 5294 } 5295 if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES 5296 || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 5297 // track mLastTouchTime as we may need to do fling at 5298 // ACTION_UP 5299 mLastTouchTime = eventTime; 5300 break; 5301 } 5302 5303 // Only lock dragging to one axis if we don't have a scale in progress. 5304 // Scaling implies free-roaming movement. Note we'll only ever get here 5305 // if mAllowPanAndScale is true. 5306 if (mScaleDetector != null && !mScaleDetector.isInProgress()) { 5307 // if it starts nearly horizontal or vertical, enforce it 5308 int ax = Math.abs(deltaX); 5309 int ay = Math.abs(deltaY); 5310 if (ax > MAX_SLOPE_FOR_DIAG * ay) { 5311 mSnapScrollMode = SNAP_X; 5312 mSnapPositive = deltaX > 0; 5313 } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { 5314 mSnapScrollMode = SNAP_Y; 5315 mSnapPositive = deltaY > 0; 5316 } 5317 } 5318 5319 mTouchMode = TOUCH_DRAG_MODE; 5320 mLastTouchX = x; 5321 mLastTouchY = y; 5322 fDeltaX = 0.0f; 5323 fDeltaY = 0.0f; 5324 deltaX = 0; 5325 deltaY = 0; 5326 5327 startDrag(); 5328 } 5329 5330 if (mDragTrackerHandler != null) { 5331 mDragTrackerHandler.dragTo(x, y); 5332 } 5333 5334 // do pan 5335 boolean done = false; 5336 boolean keepScrollBarsVisible = false; 5337 if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) { 5338 mLastTouchX = x; 5339 mLastTouchY = y; 5340 keepScrollBarsVisible = done = true; 5341 } else { 5342 if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) { 5343 int ax = Math.abs(deltaX); 5344 int ay = Math.abs(deltaY); 5345 if (mSnapScrollMode == SNAP_X) { 5346 // radical change means getting out of snap mode 5347 if (ay > MAX_SLOPE_FOR_DIAG * ax 5348 && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) { 5349 mSnapScrollMode = SNAP_NONE; 5350 } 5351 // reverse direction means lock in the snap mode 5352 if (ax > MAX_SLOPE_FOR_DIAG * ay && 5353 (mSnapPositive 5354 ? deltaX < -mMinLockSnapReverseDistance 5355 : deltaX > mMinLockSnapReverseDistance)) { 5356 mSnapScrollMode |= SNAP_LOCK; 5357 } 5358 } else { 5359 // radical change means getting out of snap mode 5360 if (ax > MAX_SLOPE_FOR_DIAG * ay 5361 && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) { 5362 mSnapScrollMode = SNAP_NONE; 5363 } 5364 // reverse direction means lock in the snap mode 5365 if (ay > MAX_SLOPE_FOR_DIAG * ax && 5366 (mSnapPositive 5367 ? deltaY < -mMinLockSnapReverseDistance 5368 : deltaY > mMinLockSnapReverseDistance)) { 5369 mSnapScrollMode |= SNAP_LOCK; 5370 } 5371 } 5372 } 5373 if (mSnapScrollMode != SNAP_NONE) { 5374 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 5375 deltaY = 0; 5376 } else { 5377 deltaX = 0; 5378 } 5379 } 5380 if ((deltaX | deltaY) != 0) { 5381 if (deltaX != 0) { 5382 mLastTouchX = x; 5383 } 5384 if (deltaY != 0) { 5385 mLastTouchY = y; 5386 } 5387 mHeldMotionless = MOTIONLESS_FALSE; 5388 } else { 5389 // keep the scrollbar on the screen even there is no 5390 // scroll 5391 mLastTouchX = x; 5392 mLastTouchY = y; 5393 keepScrollBarsVisible = true; 5394 } 5395 mLastTouchTime = eventTime; 5396 mUserScroll = true; 5397 } 5398 5399 doDrag(deltaX, deltaY); 5400 5401 if (keepScrollBarsVisible) { 5402 if (mHeldMotionless != MOTIONLESS_TRUE) { 5403 mHeldMotionless = MOTIONLESS_TRUE; 5404 invalidate(); 5405 } 5406 // keep the scrollbar on the screen even there is no scroll 5407 awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), 5408 false); 5409 // return false to indicate that we can't pan out of the 5410 // view space 5411 return !done; 5412 } 5413 break; 5414 } 5415 case MotionEvent.ACTION_UP: { 5416 // pass the touch events from UI thread to WebCore thread 5417 if (shouldForwardTouchEvent()) { 5418 TouchEventData ted = new TouchEventData(); 5419 ted.mAction = action; 5420 ted.mX = contentX; 5421 ted.mY = contentY; 5422 ted.mMetaState = ev.getMetaState(); 5423 ted.mReprocess = mDeferTouchProcess; 5424 if (mDeferTouchProcess) { 5425 ted.mViewX = x; 5426 ted.mViewY = y; 5427 } 5428 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5429 } 5430 mLastTouchUpTime = eventTime; 5431 switch (mTouchMode) { 5432 case TOUCH_DOUBLE_TAP_MODE: // double tap 5433 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5434 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5435 if (inFullScreenMode() || mDeferTouchProcess) { 5436 TouchEventData ted = new TouchEventData(); 5437 ted.mAction = WebViewCore.ACTION_DOUBLETAP; 5438 ted.mX = contentX; 5439 ted.mY = contentY; 5440 ted.mMetaState = ev.getMetaState(); 5441 ted.mReprocess = mDeferTouchProcess; 5442 if (mDeferTouchProcess) { 5443 ted.mViewX = x; 5444 ted.mViewY = y; 5445 } 5446 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5447 } else if (mPreventDefault != PREVENT_DEFAULT_YES){ 5448 doDoubleTap(); 5449 mTouchMode = TOUCH_DONE_MODE; 5450 } 5451 break; 5452 case TOUCH_INIT_MODE: // tap 5453 case TOUCH_SHORTPRESS_START_MODE: 5454 case TOUCH_SHORTPRESS_MODE: 5455 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5456 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5457 if (mConfirmMove) { 5458 Log.w(LOGTAG, "Miss a drag as we are waiting for" + 5459 " WebCore's response for touch down."); 5460 if (mPreventDefault != PREVENT_DEFAULT_YES 5461 && (computeMaxScrollX() > 0 5462 || computeMaxScrollY() > 0)) { 5463 // UI takes control back, cancel WebCore touch 5464 cancelWebCoreTouchEvent(contentX, contentY, 5465 true); 5466 // we will not rewrite drag code here, but we 5467 // will try fling if it applies. 5468 WebViewCore.reducePriority(); 5469 // to get better performance, pause updating the 5470 // picture 5471 WebViewCore.pauseUpdatePicture(mWebViewCore); 5472 // fall through to TOUCH_DRAG_MODE 5473 } else { 5474 // WebKit may consume the touch event and modify 5475 // DOM. drawContentPicture() will be called with 5476 // animateSroll as true for better performance. 5477 // Force redraw in high-quality. 5478 invalidate(); 5479 break; 5480 } 5481 } else { 5482 if (mSelectingText) { 5483 if (nativeHitSelection(contentX, contentY)) { 5484 copySelection(); 5485 } 5486 selectionDone(); 5487 break; 5488 } 5489 if (mTouchMode == TOUCH_INIT_MODE) { 5490 mPrivateHandler.sendEmptyMessageDelayed( 5491 RELEASE_SINGLE_TAP, ViewConfiguration 5492 .getDoubleTapTimeout()); 5493 } else { 5494 doShortPress(); 5495 } 5496 break; 5497 } 5498 case TOUCH_DRAG_MODE: 5499 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 5500 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 5501 // if the user waits a while w/o moving before the 5502 // up, we don't want to do a fling 5503 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { 5504 if (mVelocityTracker == null) { 5505 Log.e(LOGTAG, "Got null mVelocityTracker when " 5506 + "mPreventDefault = " 5507 + mPreventDefault 5508 + " mDeferTouchProcess = " 5509 + mDeferTouchProcess); 5510 } 5511 mVelocityTracker.addMovement(ev); 5512 // set to MOTIONLESS_IGNORE so that it won't keep 5513 // removing and sending message in 5514 // drawCoreAndCursorRing() 5515 mHeldMotionless = MOTIONLESS_IGNORE; 5516 doFling(); 5517 break; 5518 } else { 5519 if (mScroller.springBack(mScrollX, mScrollY, 0, 5520 computeMaxScrollX(), 0, 5521 computeMaxScrollY())) { 5522 invalidate(); 5523 } 5524 } 5525 // redraw in high-quality, as we're done dragging 5526 mHeldMotionless = MOTIONLESS_TRUE; 5527 invalidate(); 5528 // fall through 5529 case TOUCH_DRAG_START_MODE: 5530 // TOUCH_DRAG_START_MODE should not happen for the real 5531 // device as we almost certain will get a MOVE. But this 5532 // is possible on emulator. 5533 mLastVelocity = 0; 5534 WebViewCore.resumePriority(); 5535 WebViewCore.resumeUpdatePicture(mWebViewCore); 5536 break; 5537 } 5538 stopTouch(); 5539 break; 5540 } 5541 case MotionEvent.ACTION_CANCEL: { 5542 if (mTouchMode == TOUCH_DRAG_MODE) { 5543 mScroller.springBack(mScrollX, mScrollY, 0, 5544 computeMaxScrollX(), 0, computeMaxScrollY()); 5545 invalidate(); 5546 } 5547 cancelWebCoreTouchEvent(contentX, contentY, false); 5548 cancelTouch(); 5549 break; 5550 } 5551 } 5552 return true; 5553 } 5554 5555 private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) { 5556 if (shouldForwardTouchEvent()) { 5557 if (removeEvents) { 5558 mWebViewCore.removeMessages(EventHub.TOUCH_EVENT); 5559 } 5560 TouchEventData ted = new TouchEventData(); 5561 ted.mX = x; 5562 ted.mY = y; 5563 ted.mAction = MotionEvent.ACTION_CANCEL; 5564 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5565 mPreventDefault = PREVENT_DEFAULT_IGNORE; 5566 } 5567 } 5568 5569 private void startTouch(float x, float y, long eventTime) { 5570 // Remember where the motion event started 5571 mLastTouchX = x; 5572 mLastTouchY = y; 5573 mLastTouchTime = eventTime; 5574 mVelocityTracker = VelocityTracker.obtain(); 5575 mSnapScrollMode = SNAP_NONE; 5576 if (mDragTracker != null) { 5577 mDragTrackerHandler = new DragTrackerHandler(x, y, mDragTracker); 5578 } 5579 } 5580 5581 private void startDrag() { 5582 WebViewCore.reducePriority(); 5583 // to get better performance, pause updating the picture 5584 WebViewCore.pauseUpdatePicture(mWebViewCore); 5585 if (!mDragFromTextInput) { 5586 nativeHideCursor(); 5587 } 5588 WebSettings settings = getSettings(); 5589 if (settings.supportZoom() 5590 && settings.getBuiltInZoomControls() 5591 && !getZoomButtonsController().isVisible() 5592 && mMinZoomScale < mMaxZoomScale 5593 && (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF 5594 || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF)) { 5595 mZoomButtonsController.setVisible(true); 5596 int count = settings.getDoubleTapToastCount(); 5597 if (mInZoomOverview && count > 0) { 5598 settings.setDoubleTapToastCount(--count); 5599 Toast.makeText(mContext, 5600 com.android.internal.R.string.double_tap_toast, 5601 Toast.LENGTH_LONG).show(); 5602 } 5603 } 5604 } 5605 5606 private void doDrag(int deltaX, int deltaY) { 5607 if ((deltaX | deltaY) != 0) { 5608 final int oldX = mScrollX; 5609 final int oldY = mScrollY; 5610 final int rangeX = computeMaxScrollX(); 5611 final int rangeY = computeMaxScrollY(); 5612 5613 if (mEdgeGlowTop != null) { 5614 // Save the deltas for overscroll glow. 5615 mOverscrollDeltaX = deltaX; 5616 mOverscrollDeltaY = deltaY; 5617 } 5618 5619 overScrollBy(deltaX, deltaY, oldX, oldY, 5620 rangeX, rangeY, 5621 mOverscrollDistance, mOverscrollDistance, true); 5622 if (mEdgeGlowTop != null && 5623 (!mEdgeGlowTop.isFinished() || !mEdgeGlowBottom.isFinished() || 5624 !mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) { 5625 invalidate(); 5626 } 5627 } 5628 if (!getSettings().getBuiltInZoomControls()) { 5629 boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; 5630 if (mZoomControls != null && showPlusMinus) { 5631 if (mZoomControls.getVisibility() == View.VISIBLE) { 5632 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 5633 } else { 5634 mZoomControls.show(showPlusMinus, false); 5635 } 5636 mPrivateHandler.postDelayed(mZoomControlRunnable, 5637 ZOOM_CONTROLS_TIMEOUT); 5638 } 5639 } 5640 } 5641 5642 private void stopTouch() { 5643 if (mDragTrackerHandler != null) { 5644 mDragTrackerHandler.stopDrag(); 5645 } 5646 // we also use mVelocityTracker == null to tell us that we are 5647 // not "moving around", so we can take the slower/prettier 5648 // mode in the drawing code 5649 if (mVelocityTracker != null) { 5650 mVelocityTracker.recycle(); 5651 mVelocityTracker = null; 5652 } 5653 5654 // Release any pulled glows 5655 if (mEdgeGlowTop != null) { 5656 mEdgeGlowTop.onRelease(); 5657 mEdgeGlowBottom.onRelease(); 5658 mEdgeGlowLeft.onRelease(); 5659 mEdgeGlowRight.onRelease(); 5660 } 5661 } 5662 5663 private void cancelTouch() { 5664 if (mDragTrackerHandler != null) { 5665 mDragTrackerHandler.stopDrag(); 5666 } 5667 // we also use mVelocityTracker == null to tell us that we are 5668 // not "moving around", so we can take the slower/prettier 5669 // mode in the drawing code 5670 if (mVelocityTracker != null) { 5671 mVelocityTracker.recycle(); 5672 mVelocityTracker = null; 5673 } 5674 5675 // Release any pulled glows 5676 if (mEdgeGlowTop != null) { 5677 mEdgeGlowTop.onRelease(); 5678 mEdgeGlowBottom.onRelease(); 5679 mEdgeGlowLeft.onRelease(); 5680 mEdgeGlowRight.onRelease(); 5681 } 5682 5683 if (mTouchMode == TOUCH_DRAG_MODE) { 5684 WebViewCore.resumePriority(); 5685 WebViewCore.resumeUpdatePicture(mWebViewCore); 5686 } 5687 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5688 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5689 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 5690 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 5691 mHeldMotionless = MOTIONLESS_TRUE; 5692 mTouchMode = TOUCH_DONE_MODE; 5693 nativeHideCursor(); 5694 } 5695 5696 private long mTrackballFirstTime = 0; 5697 private long mTrackballLastTime = 0; 5698 private float mTrackballRemainsX = 0.0f; 5699 private float mTrackballRemainsY = 0.0f; 5700 private int mTrackballXMove = 0; 5701 private int mTrackballYMove = 0; 5702 private boolean mSelectingText = false; 5703 private boolean mSelectionStarted = false; 5704 private boolean mExtendSelection = false; 5705 private boolean mDrawSelectionPointer = false; 5706 private static final int TRACKBALL_KEY_TIMEOUT = 1000; 5707 private static final int TRACKBALL_TIMEOUT = 200; 5708 private static final int TRACKBALL_WAIT = 100; 5709 private static final int TRACKBALL_SCALE = 400; 5710 private static final int TRACKBALL_SCROLL_COUNT = 5; 5711 private static final int TRACKBALL_MOVE_COUNT = 10; 5712 private static final int TRACKBALL_MULTIPLIER = 3; 5713 private static final int SELECT_CURSOR_OFFSET = 16; 5714 private int mSelectX = 0; 5715 private int mSelectY = 0; 5716 private boolean mFocusSizeChanged = false; 5717 private boolean mShiftIsPressed = false; 5718 private boolean mTrackballDown = false; 5719 private long mTrackballUpTime = 0; 5720 private long mLastCursorTime = 0; 5721 private Rect mLastCursorBounds; 5722 5723 // Set by default; BrowserActivity clears to interpret trackball data 5724 // directly for movement. Currently, the framework only passes 5725 // arrow key events, not trackball events, from one child to the next 5726 private boolean mMapTrackballToArrowKeys = true; 5727 5728 public void setMapTrackballToArrowKeys(boolean setMap) { 5729 mMapTrackballToArrowKeys = setMap; 5730 } 5731 5732 void resetTrackballTime() { 5733 mTrackballLastTime = 0; 5734 } 5735 5736 @Override 5737 public boolean onTrackballEvent(MotionEvent ev) { 5738 long time = ev.getEventTime(); 5739 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) { 5740 if (ev.getY() > 0) pageDown(true); 5741 if (ev.getY() < 0) pageUp(true); 5742 return true; 5743 } 5744 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 5745 if (mSelectingText) { 5746 return true; // discard press if copy in progress 5747 } 5748 mTrackballDown = true; 5749 if (mNativeClass == 0) { 5750 return false; 5751 } 5752 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 5753 if (time - mLastCursorTime <= TRACKBALL_TIMEOUT 5754 && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { 5755 nativeSelectBestAt(mLastCursorBounds); 5756 } 5757 if (DebugFlags.WEB_VIEW) { 5758 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev 5759 + " time=" + time 5760 + " mLastCursorTime=" + mLastCursorTime); 5761 } 5762 if (isInTouchMode()) requestFocusFromTouch(); 5763 return false; // let common code in onKeyDown at it 5764 } 5765 if (ev.getAction() == MotionEvent.ACTION_UP) { 5766 // LONG_PRESS_CENTER is set in common onKeyDown 5767 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 5768 mTrackballDown = false; 5769 mTrackballUpTime = time; 5770 if (mSelectingText) { 5771 if (mExtendSelection) { 5772 copySelection(); 5773 selectionDone(); 5774 } else { 5775 mExtendSelection = true; 5776 nativeSetExtendSelection(); 5777 invalidate(); // draw the i-beam instead of the arrow 5778 } 5779 return true; // discard press if copy in progress 5780 } 5781 if (DebugFlags.WEB_VIEW) { 5782 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev 5783 + " time=" + time 5784 ); 5785 } 5786 return false; // let common code in onKeyUp at it 5787 } 5788 if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { 5789 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); 5790 return false; 5791 } 5792 if (mTrackballDown) { 5793 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit"); 5794 return true; // discard move if trackball is down 5795 } 5796 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { 5797 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); 5798 return true; 5799 } 5800 // TODO: alternatively we can do panning as touch does 5801 switchOutDrawHistory(); 5802 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { 5803 if (DebugFlags.WEB_VIEW) { 5804 Log.v(LOGTAG, "onTrackballEvent time=" 5805 + time + " last=" + mTrackballLastTime); 5806 } 5807 mTrackballFirstTime = time; 5808 mTrackballXMove = mTrackballYMove = 0; 5809 } 5810 mTrackballLastTime = time; 5811 if (DebugFlags.WEB_VIEW) { 5812 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); 5813 } 5814 mTrackballRemainsX += ev.getX(); 5815 mTrackballRemainsY += ev.getY(); 5816 doTrackball(time); 5817 return true; 5818 } 5819 5820 void moveSelection(float xRate, float yRate) { 5821 if (mNativeClass == 0) 5822 return; 5823 int width = getViewWidth(); 5824 int height = getViewHeight(); 5825 mSelectX += xRate; 5826 mSelectY += yRate; 5827 int maxX = width + mScrollX; 5828 int maxY = height + mScrollY; 5829 mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET 5830 , mSelectX)); 5831 mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET 5832 , mSelectY)); 5833 if (DebugFlags.WEB_VIEW) { 5834 Log.v(LOGTAG, "moveSelection" 5835 + " mSelectX=" + mSelectX 5836 + " mSelectY=" + mSelectY 5837 + " mScrollX=" + mScrollX 5838 + " mScrollY=" + mScrollY 5839 + " xRate=" + xRate 5840 + " yRate=" + yRate 5841 ); 5842 } 5843 nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY)); 5844 int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET 5845 : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 5846 : 0; 5847 int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET 5848 : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 5849 : 0; 5850 pinScrollBy(scrollX, scrollY, true, 0); 5851 Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); 5852 requestRectangleOnScreen(select); 5853 invalidate(); 5854 } 5855 5856 private int scaleTrackballX(float xRate, int width) { 5857 int xMove = (int) (xRate / TRACKBALL_SCALE * width); 5858 int nextXMove = xMove; 5859 if (xMove > 0) { 5860 if (xMove > mTrackballXMove) { 5861 xMove -= mTrackballXMove; 5862 } 5863 } else if (xMove < mTrackballXMove) { 5864 xMove -= mTrackballXMove; 5865 } 5866 mTrackballXMove = nextXMove; 5867 return xMove; 5868 } 5869 5870 private int scaleTrackballY(float yRate, int height) { 5871 int yMove = (int) (yRate / TRACKBALL_SCALE * height); 5872 int nextYMove = yMove; 5873 if (yMove > 0) { 5874 if (yMove > mTrackballYMove) { 5875 yMove -= mTrackballYMove; 5876 } 5877 } else if (yMove < mTrackballYMove) { 5878 yMove -= mTrackballYMove; 5879 } 5880 mTrackballYMove = nextYMove; 5881 return yMove; 5882 } 5883 5884 private int keyCodeToSoundsEffect(int keyCode) { 5885 switch(keyCode) { 5886 case KeyEvent.KEYCODE_DPAD_UP: 5887 return SoundEffectConstants.NAVIGATION_UP; 5888 case KeyEvent.KEYCODE_DPAD_RIGHT: 5889 return SoundEffectConstants.NAVIGATION_RIGHT; 5890 case KeyEvent.KEYCODE_DPAD_DOWN: 5891 return SoundEffectConstants.NAVIGATION_DOWN; 5892 case KeyEvent.KEYCODE_DPAD_LEFT: 5893 return SoundEffectConstants.NAVIGATION_LEFT; 5894 } 5895 throw new IllegalArgumentException("keyCode must be one of " + 5896 "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + 5897 "KEYCODE_DPAD_LEFT}."); 5898 } 5899 5900 private void doTrackball(long time) { 5901 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime); 5902 if (elapsed == 0) { 5903 elapsed = TRACKBALL_TIMEOUT; 5904 } 5905 float xRate = mTrackballRemainsX * 1000 / elapsed; 5906 float yRate = mTrackballRemainsY * 1000 / elapsed; 5907 int viewWidth = getViewWidth(); 5908 int viewHeight = getViewHeight(); 5909 if (mSelectingText) { 5910 if (!mDrawSelectionPointer) { 5911 // The last selection was made by touch, disabling drawing the 5912 // selection pointer. Allow the trackball to adjust the 5913 // position of the touch control. 5914 mSelectX = contentToViewX(nativeSelectionX()); 5915 mSelectY = contentToViewY(nativeSelectionY()); 5916 mDrawSelectionPointer = mExtendSelection = true; 5917 nativeSetExtendSelection(); 5918 } 5919 moveSelection(scaleTrackballX(xRate, viewWidth), 5920 scaleTrackballY(yRate, viewHeight)); 5921 mTrackballRemainsX = mTrackballRemainsY = 0; 5922 return; 5923 } 5924 float ax = Math.abs(xRate); 5925 float ay = Math.abs(yRate); 5926 float maxA = Math.max(ax, ay); 5927 if (DebugFlags.WEB_VIEW) { 5928 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed 5929 + " xRate=" + xRate 5930 + " yRate=" + yRate 5931 + " mTrackballRemainsX=" + mTrackballRemainsX 5932 + " mTrackballRemainsY=" + mTrackballRemainsY); 5933 } 5934 int width = mContentWidth - viewWidth; 5935 int height = mContentHeight - viewHeight; 5936 if (width < 0) width = 0; 5937 if (height < 0) height = 0; 5938 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); 5939 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); 5940 maxA = Math.max(ax, ay); 5941 int count = Math.max(0, (int) maxA); 5942 int oldScrollX = mScrollX; 5943 int oldScrollY = mScrollY; 5944 if (count > 0) { 5945 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? 5946 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : 5947 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : 5948 KeyEvent.KEYCODE_DPAD_RIGHT; 5949 count = Math.min(count, TRACKBALL_MOVE_COUNT); 5950 if (DebugFlags.WEB_VIEW) { 5951 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode 5952 + " count=" + count 5953 + " mTrackballRemainsX=" + mTrackballRemainsX 5954 + " mTrackballRemainsY=" + mTrackballRemainsY); 5955 } 5956 if (mNativeClass != 0 && nativeFocusIsPlugin()) { 5957 for (int i = 0; i < count; i++) { 5958 letPluginHandleNavKey(selectKeyCode, time, true); 5959 } 5960 letPluginHandleNavKey(selectKeyCode, time, false); 5961 } else if (navHandledKey(selectKeyCode, count, false, time)) { 5962 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); 5963 } 5964 mTrackballRemainsX = mTrackballRemainsY = 0; 5965 } 5966 if (count >= TRACKBALL_SCROLL_COUNT) { 5967 int xMove = scaleTrackballX(xRate, width); 5968 int yMove = scaleTrackballY(yRate, height); 5969 if (DebugFlags.WEB_VIEW) { 5970 Log.v(LOGTAG, "doTrackball pinScrollBy" 5971 + " count=" + count 5972 + " xMove=" + xMove + " yMove=" + yMove 5973 + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) 5974 + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) 5975 ); 5976 } 5977 if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { 5978 xMove = 0; 5979 } 5980 if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) { 5981 yMove = 0; 5982 } 5983 if (xMove != 0 || yMove != 0) { 5984 pinScrollBy(xMove, yMove, true, 0); 5985 } 5986 mUserScroll = true; 5987 } 5988 } 5989 5990 private int computeMaxScrollX() { 5991 return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0); 5992 } 5993 5994 private int computeMaxScrollY() { 5995 return Math.max(computeRealVerticalScrollRange() + getTitleHeight() 5996 - getViewHeightWithTitle(), 0); 5997 } 5998 5999 public void flingScroll(int vx, int vy) { 6000 mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0, 6001 computeMaxScrollY(), mOverflingDistance, mOverflingDistance); 6002 invalidate(); 6003 } 6004 6005 private void doFling() { 6006 if (mVelocityTracker == null) { 6007 return; 6008 } 6009 int maxX = computeMaxScrollX(); 6010 int maxY = computeMaxScrollY(); 6011 6012 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling); 6013 int vx = (int) mVelocityTracker.getXVelocity(); 6014 int vy = (int) mVelocityTracker.getYVelocity(); 6015 6016 if (mSnapScrollMode != SNAP_NONE) { 6017 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 6018 vy = 0; 6019 } else { 6020 vx = 0; 6021 } 6022 } 6023 if (true /* EMG release: make our fling more like Maps' */) { 6024 // maps cuts their velocity in half 6025 vx = vx * 3 / 4; 6026 vy = vy * 3 / 4; 6027 } 6028 if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { 6029 WebViewCore.resumePriority(); 6030 WebViewCore.resumeUpdatePicture(mWebViewCore); 6031 if (mScroller.springBack(mScrollX, mScrollY, 0, computeMaxScrollX(), 6032 0, computeMaxScrollY())) { 6033 invalidate(); 6034 } 6035 return; 6036 } 6037 float currentVelocity = mScroller.getCurrVelocity(); 6038 if (mLastVelocity > 0 && currentVelocity > 0) { 6039 float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX) 6040 - Math.atan2(vy, vx))); 6041 final float circle = (float) (Math.PI) * 2.0f; 6042 if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) { 6043 vx += currentVelocity * mLastVelX / mLastVelocity; 6044 vy += currentVelocity * mLastVelY / mLastVelocity; 6045 if (DebugFlags.WEB_VIEW) { 6046 Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy); 6047 } 6048 } else if (DebugFlags.WEB_VIEW) { 6049 Log.v(LOGTAG, "doFling missed " + deltaR / circle); 6050 } 6051 } else if (DebugFlags.WEB_VIEW) { 6052 Log.v(LOGTAG, "doFling start last=" + mLastVelocity 6053 + " current=" + currentVelocity 6054 + " vx=" + vx + " vy=" + vy 6055 + " maxX=" + maxX + " maxY=" + maxY 6056 + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY); 6057 } 6058 6059 // Allow sloppy flings without overscrolling at the edges. 6060 if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) { 6061 vx = 0; 6062 } 6063 if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) { 6064 vy = 0; 6065 } 6066 6067 if (mOverscrollDistance < mOverflingDistance) { 6068 if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) { 6069 vx = 0; 6070 } 6071 if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) { 6072 vy = 0; 6073 } 6074 } 6075 6076 mLastVelX = vx; 6077 mLastVelY = vy; 6078 mLastVelocity = (float) Math.hypot(vx, vy); 6079 6080 // no horizontal overscroll if the content just fits 6081 mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY, 6082 maxX == 0 ? 0 : mOverflingDistance, mOverflingDistance); 6083 // Duration is calculated based on velocity. With range boundaries and overscroll 6084 // we may not know how long the final animation will take. (Hence the deprecation 6085 // warning on the call below.) It's not a big deal for scroll bars but if webcore 6086 // resumes during this effect we will take a performance hit. See computeScroll; 6087 // we resume webcore there when the animation is finished. 6088 final int time = mScroller.getDuration(); 6089 awakenScrollBars(time); 6090 invalidate(); 6091 } 6092 6093 private boolean zoomWithPreview(float scale, boolean updateTextWrapScale) { 6094 float oldScale = mActualScale; 6095 mInitialScrollX = mScrollX; 6096 mInitialScrollY = mScrollY; 6097 6098 // snap to DEFAULT_SCALE if it is close 6099 if (Math.abs(scale - mDefaultScale) < MINIMUM_SCALE_INCREMENT) { 6100 scale = mDefaultScale; 6101 } 6102 6103 setNewZoomScale(scale, updateTextWrapScale, false); 6104 6105 if (oldScale != mActualScale) { 6106 // use mZoomPickerScale to see zoom preview first 6107 mZoomStart = SystemClock.uptimeMillis(); 6108 mInvInitialZoomScale = 1.0f / oldScale; 6109 mInvFinalZoomScale = 1.0f / mActualScale; 6110 mZoomScale = mActualScale; 6111 WebViewCore.pauseUpdatePicture(mWebViewCore); 6112 invalidate(); 6113 return true; 6114 } else { 6115 return false; 6116 } 6117 } 6118 6119 /** 6120 * Returns a view containing zoom controls i.e. +/- buttons. The caller is 6121 * in charge of installing this view to the view hierarchy. This view will 6122 * become visible when the user starts scrolling via touch and fade away if 6123 * the user does not interact with it. 6124 * <p/> 6125 * API version 3 introduces a built-in zoom mechanism that is shown 6126 * automatically by the MapView. This is the preferred approach for 6127 * showing the zoom UI. 6128 * 6129 * @deprecated The built-in zoom mechanism is preferred, see 6130 * {@link WebSettings#setBuiltInZoomControls(boolean)}. 6131 */ 6132 @Deprecated 6133 public View getZoomControls() { 6134 if (!getSettings().supportZoom()) { 6135 Log.w(LOGTAG, "This WebView doesn't support zoom."); 6136 return null; 6137 } 6138 if (mZoomControls == null) { 6139 mZoomControls = createZoomControls(); 6140 6141 /* 6142 * need to be set to VISIBLE first so that getMeasuredHeight() in 6143 * {@link #onSizeChanged()} can return the measured value for proper 6144 * layout. 6145 */ 6146 mZoomControls.setVisibility(View.VISIBLE); 6147 mZoomControlRunnable = new Runnable() { 6148 public void run() { 6149 6150 /* Don't dismiss the controls if the user has 6151 * focus on them. Wait and check again later. 6152 */ 6153 if (!mZoomControls.hasFocus()) { 6154 mZoomControls.hide(); 6155 } else { 6156 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6157 mPrivateHandler.postDelayed(mZoomControlRunnable, 6158 ZOOM_CONTROLS_TIMEOUT); 6159 } 6160 } 6161 }; 6162 } 6163 return mZoomControls; 6164 } 6165 6166 private ExtendedZoomControls createZoomControls() { 6167 ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext 6168 , null); 6169 zoomControls.setOnZoomInClickListener(new OnClickListener() { 6170 public void onClick(View v) { 6171 // reset time out 6172 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6173 mPrivateHandler.postDelayed(mZoomControlRunnable, 6174 ZOOM_CONTROLS_TIMEOUT); 6175 zoomIn(); 6176 } 6177 }); 6178 zoomControls.setOnZoomOutClickListener(new OnClickListener() { 6179 public void onClick(View v) { 6180 // reset time out 6181 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6182 mPrivateHandler.postDelayed(mZoomControlRunnable, 6183 ZOOM_CONTROLS_TIMEOUT); 6184 zoomOut(); 6185 } 6186 }); 6187 return zoomControls; 6188 } 6189 6190 /** 6191 * Gets the {@link ZoomButtonsController} which can be used to add 6192 * additional buttons to the zoom controls window. 6193 * 6194 * @return The instance of {@link ZoomButtonsController} used by this class, 6195 * or null if it is unavailable. 6196 * @hide 6197 */ 6198 public ZoomButtonsController getZoomButtonsController() { 6199 if (mZoomButtonsController == null) { 6200 mZoomButtonsController = new ZoomButtonsController(this); 6201 mZoomButtonsController.setOnZoomListener(mZoomListener); 6202 // ZoomButtonsController positions the buttons at the bottom, but in 6203 // the middle. Change their layout parameters so they appear on the 6204 // right. 6205 View controls = mZoomButtonsController.getZoomControls(); 6206 ViewGroup.LayoutParams params = controls.getLayoutParams(); 6207 if (params instanceof FrameLayout.LayoutParams) { 6208 FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams) params; 6209 frameParams.gravity = Gravity.RIGHT; 6210 } 6211 } 6212 return mZoomButtonsController; 6213 } 6214 6215 /** 6216 * Perform zoom in in the webview 6217 * @return TRUE if zoom in succeeds. FALSE if no zoom changes. 6218 */ 6219 public boolean zoomIn() { 6220 // TODO: alternatively we can disallow this during draw history mode 6221 switchOutDrawHistory(); 6222 mInZoomOverview = false; 6223 // Center zooming to the center of the screen. 6224 mZoomCenterX = getViewWidth() * .5f; 6225 mZoomCenterY = getViewHeight() * .5f; 6226 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 6227 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 6228 return zoomWithPreview(mActualScale * 1.25f, true); 6229 } 6230 6231 /** 6232 * Perform zoom out in the webview 6233 * @return TRUE if zoom out succeeds. FALSE if no zoom changes. 6234 */ 6235 public boolean zoomOut() { 6236 // TODO: alternatively we can disallow this during draw history mode 6237 switchOutDrawHistory(); 6238 // Center zooming to the center of the screen. 6239 mZoomCenterX = getViewWidth() * .5f; 6240 mZoomCenterY = getViewHeight() * .5f; 6241 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 6242 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 6243 return zoomWithPreview(mActualScale * 0.8f, true); 6244 } 6245 6246 private void updateSelection() { 6247 if (mNativeClass == 0) { 6248 return; 6249 } 6250 // mLastTouchX and mLastTouchY are the point in the current viewport 6251 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 6252 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 6253 Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop, 6254 contentX + mNavSlop, contentY + mNavSlop); 6255 nativeSelectBestAt(rect); 6256 } 6257 6258 /** 6259 * Scroll the focused text field/area to match the WebTextView 6260 * @param xPercent New x position of the WebTextView from 0 to 1. 6261 * @param y New y position of the WebTextView in view coordinates 6262 */ 6263 /*package*/ void scrollFocusedTextInput(float xPercent, int y) { 6264 if (!inEditingMode() || mWebViewCore == null) { 6265 return; 6266 } 6267 mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 6268 // Since this position is relative to the top of the text input 6269 // field, we do not need to take the title bar's height into 6270 // consideration. 6271 viewToContentDimension(y), 6272 new Float(xPercent)); 6273 } 6274 6275 /** 6276 * Set our starting point and time for a drag from the WebTextView. 6277 */ 6278 /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) { 6279 if (!inEditingMode()) { 6280 return; 6281 } 6282 mLastTouchX = x + (float) (mWebTextView.getLeft() - mScrollX); 6283 mLastTouchY = y + (float) (mWebTextView.getTop() - mScrollY); 6284 mLastTouchTime = eventTime; 6285 if (!mScroller.isFinished()) { 6286 abortAnimation(); 6287 mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); 6288 } 6289 mSnapScrollMode = SNAP_NONE; 6290 mVelocityTracker = VelocityTracker.obtain(); 6291 mTouchMode = TOUCH_DRAG_START_MODE; 6292 } 6293 6294 /** 6295 * Given a motion event from the WebTextView, set its location to our 6296 * coordinates, and handle the event. 6297 */ 6298 /*package*/ boolean textFieldDrag(MotionEvent event) { 6299 if (!inEditingMode()) { 6300 return false; 6301 } 6302 mDragFromTextInput = true; 6303 event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX), 6304 (float) (mWebTextView.getTop() - mScrollY)); 6305 boolean result = onTouchEvent(event); 6306 mDragFromTextInput = false; 6307 return result; 6308 } 6309 6310 /** 6311 * Due a touch up from a WebTextView. This will be handled by webkit to 6312 * change the selection. 6313 * @param event MotionEvent in the WebTextView's coordinates. 6314 */ 6315 /*package*/ void touchUpOnTextField(MotionEvent event) { 6316 if (!inEditingMode()) { 6317 return; 6318 } 6319 int x = viewToContentX((int) event.getX() + mWebTextView.getLeft()); 6320 int y = viewToContentY((int) event.getY() + mWebTextView.getTop()); 6321 nativeMotionUp(x, y, mNavSlop); 6322 } 6323 6324 /** 6325 * Called when pressing the center key or trackball on a textfield. 6326 */ 6327 /*package*/ void centerKeyPressOnTextField() { 6328 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 6329 nativeCursorNodePointer()); 6330 } 6331 6332 private void doShortPress() { 6333 if (mNativeClass == 0) { 6334 return; 6335 } 6336 if (mPreventDefault == PREVENT_DEFAULT_YES) { 6337 return; 6338 } 6339 mTouchMode = TOUCH_DONE_MODE; 6340 switchOutDrawHistory(); 6341 // mLastTouchX and mLastTouchY are the point in the current viewport 6342 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 6343 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 6344 if (nativePointInNavCache(contentX, contentY, mNavSlop)) { 6345 WebViewCore.MotionUpData motionUpData = new WebViewCore 6346 .MotionUpData(); 6347 motionUpData.mFrame = nativeCacheHitFramePointer(); 6348 motionUpData.mNode = nativeCacheHitNodePointer(); 6349 motionUpData.mBounds = nativeCacheHitNodeBounds(); 6350 motionUpData.mX = contentX; 6351 motionUpData.mY = contentY; 6352 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS, 6353 motionUpData); 6354 } else { 6355 doMotionUp(contentX, contentY); 6356 } 6357 } 6358 6359 private void doMotionUp(int contentX, int contentY) { 6360 if (mLogEvent && nativeMotionUp(contentX, contentY, mNavSlop)) { 6361 EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER); 6362 } 6363 if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { 6364 playSoundEffect(SoundEffectConstants.CLICK); 6365 } 6366 } 6367 6368 /* 6369 * Return true if the view (Plugin) is fully visible and maximized inside 6370 * the WebView. 6371 */ 6372 private boolean isPluginFitOnScreen(ViewManager.ChildView view) { 6373 int viewWidth = getViewWidth(); 6374 int viewHeight = getViewHeightWithTitle(); 6375 float scale = Math.min((float) viewWidth / view.width, 6376 (float) viewHeight / view.height); 6377 if (scale < mMinZoomScale) { 6378 scale = mMinZoomScale; 6379 } else if (scale > mMaxZoomScale) { 6380 scale = mMaxZoomScale; 6381 } 6382 if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) { 6383 if (contentToViewX(view.x) >= mScrollX 6384 && contentToViewX(view.x + view.width) <= mScrollX 6385 + viewWidth 6386 && contentToViewY(view.y) >= mScrollY 6387 && contentToViewY(view.y + view.height) <= mScrollY 6388 + viewHeight) { 6389 return true; 6390 } 6391 } 6392 return false; 6393 } 6394 6395 /* 6396 * Maximize and center the rectangle, specified in the document coordinate 6397 * space, inside the WebView. If the zoom doesn't need to be changed, do an 6398 * animated scroll to center it. If the zoom needs to be changed, find the 6399 * zoom center and do a smooth zoom transition. 6400 */ 6401 private void centerFitRect(int docX, int docY, int docWidth, int docHeight) { 6402 int viewWidth = getViewWidth(); 6403 int viewHeight = getViewHeightWithTitle(); 6404 float scale = Math.min((float) viewWidth / docWidth, (float) viewHeight 6405 / docHeight); 6406 if (scale < mMinZoomScale) { 6407 scale = mMinZoomScale; 6408 } else if (scale > mMaxZoomScale) { 6409 scale = mMaxZoomScale; 6410 } 6411 if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) { 6412 pinScrollTo(contentToViewX(docX + docWidth / 2) - viewWidth / 2, 6413 contentToViewY(docY + docHeight / 2) - viewHeight / 2, 6414 true, 0); 6415 } else { 6416 float oldScreenX = docX * mActualScale - mScrollX; 6417 float rectViewX = docX * scale; 6418 float rectViewWidth = docWidth * scale; 6419 float newMaxWidth = mContentWidth * scale; 6420 float newScreenX = (viewWidth - rectViewWidth) / 2; 6421 // pin the newX to the WebView 6422 if (newScreenX > rectViewX) { 6423 newScreenX = rectViewX; 6424 } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) { 6425 newScreenX = viewWidth - (newMaxWidth - rectViewX); 6426 } 6427 mZoomCenterX = (oldScreenX * scale - newScreenX * mActualScale) 6428 / (scale - mActualScale); 6429 float oldScreenY = docY * mActualScale + getTitleHeight() 6430 - mScrollY; 6431 float rectViewY = docY * scale + getTitleHeight(); 6432 float rectViewHeight = docHeight * scale; 6433 float newMaxHeight = mContentHeight * scale + getTitleHeight(); 6434 float newScreenY = (viewHeight - rectViewHeight) / 2; 6435 // pin the newY to the WebView 6436 if (newScreenY > rectViewY) { 6437 newScreenY = rectViewY; 6438 } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) { 6439 newScreenY = viewHeight - (newMaxHeight - rectViewY); 6440 } 6441 mZoomCenterY = (oldScreenY * scale - newScreenY * mActualScale) 6442 / (scale - mActualScale); 6443 zoomWithPreview(scale, false); 6444 } 6445 } 6446 6447 void dismissZoomControl() { 6448 if (mWebViewCore == null) { 6449 // maybe called after WebView's destroy(). As we can't get settings, 6450 // just hide zoom control for both styles. 6451 if (mZoomButtonsController != null) { 6452 mZoomButtonsController.setVisible(false); 6453 } 6454 if (mZoomControls != null) { 6455 mZoomControls.hide(); 6456 } 6457 return; 6458 } 6459 WebSettings settings = getSettings(); 6460 if (settings.getBuiltInZoomControls()) { 6461 if (mZoomButtonsController != null) { 6462 mZoomButtonsController.setVisible(false); 6463 } 6464 } else { 6465 if (mZoomControlRunnable != null) { 6466 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 6467 } 6468 if (mZoomControls != null) { 6469 mZoomControls.hide(); 6470 } 6471 } 6472 } 6473 6474 // Rule for double tap: 6475 // 1. if the current scale is not same as the text wrap scale and layout 6476 // algorithm is NARROW_COLUMNS, fit to column; 6477 // 2. if the current state is not overview mode, change to overview mode; 6478 // 3. if the current state is overview mode, change to default scale. 6479 private void doDoubleTap() { 6480 if (mWebViewCore.getSettings().getUseWideViewPort() == false) { 6481 return; 6482 } 6483 mZoomCenterX = mLastTouchX; 6484 mZoomCenterY = mLastTouchY; 6485 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 6486 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 6487 WebSettings settings = getSettings(); 6488 settings.setDoubleTapToastCount(0); 6489 // remove the zoom control after double tap 6490 dismissZoomControl(); 6491 ViewManager.ChildView plugin = mViewManager.hitTest(mAnchorX, mAnchorY); 6492 if (plugin != null) { 6493 if (isPluginFitOnScreen(plugin)) { 6494 mInZoomOverview = true; 6495 // Force the titlebar fully reveal in overview mode 6496 if (mScrollY < getTitleHeight()) mScrollY = 0; 6497 zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth, 6498 true); 6499 } else { 6500 mInZoomOverview = false; 6501 centerFitRect(plugin.x, plugin.y, plugin.width, plugin.height); 6502 } 6503 return; 6504 } 6505 boolean zoomToDefault = false; 6506 if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS) 6507 && (Math.abs(mActualScale - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT)) { 6508 setNewZoomScale(mActualScale, true, true); 6509 float overviewScale = (float) getViewWidth() / mZoomOverviewWidth; 6510 if (Math.abs(mActualScale - overviewScale) < MINIMUM_SCALE_INCREMENT) { 6511 mInZoomOverview = true; 6512 } 6513 } else if (!mInZoomOverview) { 6514 float newScale = (float) getViewWidth() / mZoomOverviewWidth; 6515 if (Math.abs(mActualScale - newScale) >= MINIMUM_SCALE_INCREMENT) { 6516 mInZoomOverview = true; 6517 // Force the titlebar fully reveal in overview mode 6518 if (mScrollY < getTitleHeight()) mScrollY = 0; 6519 zoomWithPreview(newScale, true); 6520 } else if (Math.abs(mActualScale - mDefaultScale) >= MINIMUM_SCALE_INCREMENT) { 6521 zoomToDefault = true; 6522 } 6523 } else { 6524 zoomToDefault = true; 6525 } 6526 if (zoomToDefault) { 6527 mInZoomOverview = false; 6528 int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale); 6529 if (left != NO_LEFTEDGE) { 6530 // add a 5pt padding to the left edge. 6531 int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5)) 6532 - mScrollX; 6533 // Re-calculate the zoom center so that the new scroll x will be 6534 // on the left edge. 6535 if (viewLeft > 0) { 6536 mZoomCenterX = viewLeft * mDefaultScale 6537 / (mDefaultScale - mActualScale); 6538 } else { 6539 scrollBy(viewLeft, 0); 6540 mZoomCenterX = 0; 6541 } 6542 } 6543 zoomWithPreview(mDefaultScale, true); 6544 } 6545 } 6546 6547 // Called by JNI to handle a touch on a node representing an email address, 6548 // address, or phone number 6549 private void overrideLoading(String url) { 6550 mCallbackProxy.uiOverrideUrlLoading(url); 6551 } 6552 6553 @Override 6554 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 6555 boolean result = false; 6556 if (inEditingMode()) { 6557 result = mWebTextView.requestFocus(direction, 6558 previouslyFocusedRect); 6559 } else { 6560 result = super.requestFocus(direction, previouslyFocusedRect); 6561 if (mWebViewCore.getSettings().getNeedInitialFocus()) { 6562 // For cases such as GMail, where we gain focus from a direction, 6563 // we want to move to the first available link. 6564 // FIXME: If there are no visible links, we may not want to 6565 int fakeKeyDirection = 0; 6566 switch(direction) { 6567 case View.FOCUS_UP: 6568 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP; 6569 break; 6570 case View.FOCUS_DOWN: 6571 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN; 6572 break; 6573 case View.FOCUS_LEFT: 6574 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT; 6575 break; 6576 case View.FOCUS_RIGHT: 6577 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT; 6578 break; 6579 default: 6580 return result; 6581 } 6582 if (mNativeClass != 0 && !nativeHasCursorNode()) { 6583 navHandledKey(fakeKeyDirection, 1, true, 0); 6584 } 6585 } 6586 } 6587 return result; 6588 } 6589 6590 @Override 6591 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 6592 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 6593 6594 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 6595 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 6596 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 6597 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 6598 6599 int measuredHeight = heightSize; 6600 int measuredWidth = widthSize; 6601 6602 // Grab the content size from WebViewCore. 6603 int contentHeight = contentToViewDimension(mContentHeight); 6604 int contentWidth = contentToViewDimension(mContentWidth); 6605 6606// Log.d(LOGTAG, "------- measure " + heightMode); 6607 6608 if (heightMode != MeasureSpec.EXACTLY) { 6609 mHeightCanMeasure = true; 6610 measuredHeight = contentHeight; 6611 if (heightMode == MeasureSpec.AT_MOST) { 6612 // If we are larger than the AT_MOST height, then our height can 6613 // no longer be measured and we should scroll internally. 6614 if (measuredHeight > heightSize) { 6615 measuredHeight = heightSize; 6616 mHeightCanMeasure = false; 6617 } 6618 } 6619 } else { 6620 mHeightCanMeasure = false; 6621 } 6622 if (mNativeClass != 0) { 6623 nativeSetHeightCanMeasure(mHeightCanMeasure); 6624 } 6625 // For the width, always use the given size unless unspecified. 6626 if (widthMode == MeasureSpec.UNSPECIFIED) { 6627 mWidthCanMeasure = true; 6628 measuredWidth = contentWidth; 6629 } else { 6630 mWidthCanMeasure = false; 6631 } 6632 6633 synchronized (this) { 6634 setMeasuredDimension(measuredWidth, measuredHeight); 6635 } 6636 } 6637 6638 @Override 6639 public boolean requestChildRectangleOnScreen(View child, 6640 Rect rect, 6641 boolean immediate) { 6642 rect.offset(child.getLeft() - child.getScrollX(), 6643 child.getTop() - child.getScrollY()); 6644 6645 Rect content = new Rect(viewToContentX(mScrollX), 6646 viewToContentY(mScrollY), 6647 viewToContentX(mScrollX + getWidth() 6648 - getVerticalScrollbarWidth()), 6649 viewToContentY(mScrollY + getViewHeightWithTitle())); 6650 content = nativeSubtractLayers(content); 6651 int screenTop = contentToViewY(content.top); 6652 int screenBottom = contentToViewY(content.bottom); 6653 int height = screenBottom - screenTop; 6654 int scrollYDelta = 0; 6655 6656 if (rect.bottom > screenBottom) { 6657 int oneThirdOfScreenHeight = height / 3; 6658 if (rect.height() > 2 * oneThirdOfScreenHeight) { 6659 // If the rectangle is too tall to fit in the bottom two thirds 6660 // of the screen, place it at the top. 6661 scrollYDelta = rect.top - screenTop; 6662 } else { 6663 // If the rectangle will still fit on screen, we want its 6664 // top to be in the top third of the screen. 6665 scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight); 6666 } 6667 } else if (rect.top < screenTop) { 6668 scrollYDelta = rect.top - screenTop; 6669 } 6670 6671 int screenLeft = contentToViewX(content.left); 6672 int screenRight = contentToViewX(content.right); 6673 int width = screenRight - screenLeft; 6674 int scrollXDelta = 0; 6675 6676 if (rect.right > screenRight && rect.left > screenLeft) { 6677 if (rect.width() > width) { 6678 scrollXDelta += (rect.left - screenLeft); 6679 } else { 6680 scrollXDelta += (rect.right - screenRight); 6681 } 6682 } else if (rect.left < screenLeft) { 6683 scrollXDelta -= (screenLeft - rect.left); 6684 } 6685 6686 if ((scrollYDelta | scrollXDelta) != 0) { 6687 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0); 6688 } 6689 6690 return false; 6691 } 6692 6693 /* package */ void replaceTextfieldText(int oldStart, int oldEnd, 6694 String replace, int newStart, int newEnd) { 6695 WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData(); 6696 arg.mReplace = replace; 6697 arg.mNewStart = newStart; 6698 arg.mNewEnd = newEnd; 6699 mTextGeneration++; 6700 arg.mTextGeneration = mTextGeneration; 6701 mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); 6702 } 6703 6704 /* package */ void passToJavaScript(String currentText, KeyEvent event) { 6705 WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData(); 6706 arg.mEvent = event; 6707 arg.mCurrentText = currentText; 6708 // Increase our text generation number, and pass it to webcore thread 6709 mTextGeneration++; 6710 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); 6711 // WebKit's document state is not saved until about to leave the page. 6712 // To make sure the host application, like Browser, has the up to date 6713 // document state when it goes to background, we force to save the 6714 // document state. 6715 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); 6716 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, 6717 cursorData(), 1000); 6718 } 6719 6720 /* package */ synchronized WebViewCore getWebViewCore() { 6721 return mWebViewCore; 6722 } 6723 6724 //------------------------------------------------------------------------- 6725 // Methods can be called from a separate thread, like WebViewCore 6726 // If it needs to call the View system, it has to send message. 6727 //------------------------------------------------------------------------- 6728 6729 /** 6730 * General handler to receive message coming from webkit thread 6731 */ 6732 class PrivateHandler extends Handler { 6733 @Override 6734 public void handleMessage(Message msg) { 6735 // exclude INVAL_RECT_MSG_ID since it is frequently output 6736 if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) { 6737 if (msg.what >= FIRST_PRIVATE_MSG_ID 6738 && msg.what <= LAST_PRIVATE_MSG_ID) { 6739 Log.v(LOGTAG, HandlerPrivateDebugString[msg.what 6740 - FIRST_PRIVATE_MSG_ID]); 6741 } else if (msg.what >= FIRST_PACKAGE_MSG_ID 6742 && msg.what <= LAST_PACKAGE_MSG_ID) { 6743 Log.v(LOGTAG, HandlerPackageDebugString[msg.what 6744 - FIRST_PACKAGE_MSG_ID]); 6745 } else { 6746 Log.v(LOGTAG, Integer.toString(msg.what)); 6747 } 6748 } 6749 if (mWebViewCore == null) { 6750 // after WebView's destroy() is called, skip handling messages. 6751 return; 6752 } 6753 switch (msg.what) { 6754 case REMEMBER_PASSWORD: { 6755 mDatabase.setUsernamePassword( 6756 msg.getData().getString("host"), 6757 msg.getData().getString("username"), 6758 msg.getData().getString("password")); 6759 ((Message) msg.obj).sendToTarget(); 6760 break; 6761 } 6762 case NEVER_REMEMBER_PASSWORD: { 6763 mDatabase.setUsernamePassword( 6764 msg.getData().getString("host"), null, null); 6765 ((Message) msg.obj).sendToTarget(); 6766 break; 6767 } 6768 case PREVENT_DEFAULT_TIMEOUT: { 6769 // if timeout happens, cancel it so that it won't block UI 6770 // to continue handling touch events 6771 if ((msg.arg1 == MotionEvent.ACTION_DOWN 6772 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) 6773 || (msg.arg1 == MotionEvent.ACTION_MOVE 6774 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) { 6775 cancelWebCoreTouchEvent( 6776 viewToContentX((int) mLastTouchX + mScrollX), 6777 viewToContentY((int) mLastTouchY + mScrollY), 6778 true); 6779 } 6780 break; 6781 } 6782 case SWITCH_TO_SHORTPRESS: { 6783 if (mTouchMode == TOUCH_INIT_MODE) { 6784 if (mPreventDefault != PREVENT_DEFAULT_YES) { 6785 mTouchMode = TOUCH_SHORTPRESS_START_MODE; 6786 updateSelection(); 6787 } else { 6788 // set to TOUCH_SHORTPRESS_MODE so that it won't 6789 // trigger double tap any more 6790 mTouchMode = TOUCH_SHORTPRESS_MODE; 6791 } 6792 } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 6793 mTouchMode = TOUCH_DONE_MODE; 6794 } 6795 break; 6796 } 6797 case SWITCH_TO_LONGPRESS: { 6798 if (inFullScreenMode() || mDeferTouchProcess) { 6799 TouchEventData ted = new TouchEventData(); 6800 ted.mAction = WebViewCore.ACTION_LONGPRESS; 6801 ted.mX = viewToContentX((int) mLastTouchX + mScrollX); 6802 ted.mY = viewToContentY((int) mLastTouchY + mScrollY); 6803 // metaState for long press is tricky. Should it be the 6804 // state when the press started or when the press was 6805 // released? Or some intermediary key state? For 6806 // simplicity for now, we don't set it. 6807 ted.mMetaState = 0; 6808 ted.mReprocess = mDeferTouchProcess; 6809 if (mDeferTouchProcess) { 6810 ted.mViewX = mLastTouchX; 6811 ted.mViewY = mLastTouchY; 6812 } 6813 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 6814 } else if (mPreventDefault != PREVENT_DEFAULT_YES) { 6815 mTouchMode = TOUCH_DONE_MODE; 6816 performLongClick(); 6817 rebuildWebTextView(); 6818 } 6819 break; 6820 } 6821 case RELEASE_SINGLE_TAP: { 6822 doShortPress(); 6823 break; 6824 } 6825 case SCROLL_BY_MSG_ID: 6826 setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj); 6827 break; 6828 case SYNC_SCROLL_TO_MSG_ID: 6829 if (mUserScroll) { 6830 // if user has scrolled explicitly, don't sync the 6831 // scroll position any more 6832 mUserScroll = false; 6833 break; 6834 } 6835 // fall through 6836 case SCROLL_TO_MSG_ID: 6837 if (setContentScrollTo(msg.arg1, msg.arg2)) { 6838 // if we can't scroll to the exact position due to pin, 6839 // send a message to WebCore to re-scroll when we get a 6840 // new picture 6841 mUserScroll = false; 6842 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, 6843 msg.arg1, msg.arg2); 6844 } 6845 break; 6846 case SPAWN_SCROLL_TO_MSG_ID: 6847 spawnContentScrollTo(msg.arg1, msg.arg2); 6848 break; 6849 case UPDATE_ZOOM_RANGE: { 6850 WebViewCore.RestoreState restoreState 6851 = (WebViewCore.RestoreState) msg.obj; 6852 // mScrollX contains the new minPrefWidth 6853 updateZoomRange(restoreState, getViewWidth(), 6854 restoreState.mScrollX, false); 6855 break; 6856 } 6857 case NEW_PICTURE_MSG_ID: { 6858 // If we've previously delayed deleting a root 6859 // layer, do it now. 6860 if (mDelayedDeleteRootLayer) { 6861 mDelayedDeleteRootLayer = false; 6862 nativeSetRootLayer(0); 6863 } 6864 WebSettings settings = mWebViewCore.getSettings(); 6865 // called for new content 6866 final int viewWidth = getViewWidth(); 6867 final WebViewCore.DrawData draw = 6868 (WebViewCore.DrawData) msg.obj; 6869 final Point viewSize = draw.mViewPoint; 6870 boolean useWideViewport = settings.getUseWideViewPort(); 6871 WebViewCore.RestoreState restoreState = draw.mRestoreState; 6872 boolean hasRestoreState = restoreState != null; 6873 if (hasRestoreState) { 6874 updateZoomRange(restoreState, viewSize.x, 6875 draw.mMinPrefWidth, true); 6876 if (!mDrawHistory) { 6877 mInZoomOverview = false; 6878 6879 if (mInitialScaleInPercent > 0) { 6880 setNewZoomScale(mInitialScaleInPercent / 100.0f, 6881 mInitialScaleInPercent != mTextWrapScale * 100, 6882 false); 6883 } else if (restoreState.mViewScale > 0) { 6884 mTextWrapScale = restoreState.mTextWrapScale; 6885 setNewZoomScale(restoreState.mViewScale, false, 6886 false); 6887 } else { 6888 mInZoomOverview = useWideViewport 6889 && settings.getLoadWithOverviewMode(); 6890 float scale; 6891 if (mInZoomOverview) { 6892 scale = (float) viewWidth 6893 / DEFAULT_VIEWPORT_WIDTH; 6894 } else { 6895 scale = restoreState.mTextWrapScale; 6896 } 6897 setNewZoomScale(scale, Math.abs(scale 6898 - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT, 6899 false); 6900 } 6901 setContentScrollTo(restoreState.mScrollX, 6902 restoreState.mScrollY); 6903 // As we are on a new page, remove the WebTextView. This 6904 // is necessary for page loads driven by webkit, and in 6905 // particular when the user was on a password field, so 6906 // the WebTextView was visible. 6907 clearTextEntry(false); 6908 // update the zoom buttons as the scale can be changed 6909 if (getSettings().getBuiltInZoomControls()) { 6910 updateZoomButtonsEnabled(); 6911 } 6912 } 6913 } 6914 // We update the layout (i.e. request a layout from the 6915 // view system) if the last view size that we sent to 6916 // WebCore matches the view size of the picture we just 6917 // received in the fixed dimension. 6918 final boolean updateLayout = viewSize.x == mLastWidthSent 6919 && viewSize.y == mLastHeightSent; 6920 recordNewContentSize(draw.mWidthHeight.x, 6921 draw.mWidthHeight.y 6922 + (mFindIsUp ? mFindHeight : 0), updateLayout); 6923 if (DebugFlags.WEB_VIEW) { 6924 Rect b = draw.mInvalRegion.getBounds(); 6925 Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + 6926 b.left+","+b.top+","+b.right+","+b.bottom+"}"); 6927 } 6928 invalidateContentRect(draw.mInvalRegion.getBounds()); 6929 if (mPictureListener != null) { 6930 mPictureListener.onNewPicture(WebView.this, capturePicture()); 6931 } 6932 if (useWideViewport) { 6933 // limit mZoomOverviewWidth upper bound to 6934 // sMaxViewportWidth so that if the page doesn't behave 6935 // well, the WebView won't go insane. limit the lower 6936 // bound to match the default scale for mobile sites. 6937 mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math 6938 .max((int) (viewWidth / mDefaultScale), Math 6939 .max(draw.mMinPrefWidth, 6940 draw.mViewPoint.x))); 6941 } 6942 if (!mMinZoomScaleFixed) { 6943 mMinZoomScale = (float) viewWidth / mZoomOverviewWidth; 6944 } 6945 if (!mDrawHistory && mInZoomOverview) { 6946 // fit the content width to the current view. Ignore 6947 // the rounding error case. 6948 if (Math.abs((viewWidth * mInvActualScale) 6949 - mZoomOverviewWidth) > 1) { 6950 setNewZoomScale((float) viewWidth 6951 / mZoomOverviewWidth, Math.abs(mActualScale 6952 - mTextWrapScale) < MINIMUM_SCALE_INCREMENT, 6953 false); 6954 } 6955 } 6956 if (draw.mFocusSizeChanged && inEditingMode()) { 6957 mFocusSizeChanged = true; 6958 } 6959 if (hasRestoreState) { 6960 mViewManager.postReadyToDrawAll(); 6961 } 6962 break; 6963 } 6964 case WEBCORE_INITIALIZED_MSG_ID: 6965 // nativeCreate sets mNativeClass to a non-zero value 6966 nativeCreate(msg.arg1); 6967 break; 6968 case UPDATE_TEXTFIELD_TEXT_MSG_ID: 6969 // Make sure that the textfield is currently focused 6970 // and representing the same node as the pointer. 6971 if (inEditingMode() && 6972 mWebTextView.isSameTextField(msg.arg1)) { 6973 if (msg.getData().getBoolean("password")) { 6974 Spannable text = (Spannable) mWebTextView.getText(); 6975 int start = Selection.getSelectionStart(text); 6976 int end = Selection.getSelectionEnd(text); 6977 mWebTextView.setInPassword(true); 6978 // Restore the selection, which may have been 6979 // ruined by setInPassword. 6980 Spannable pword = 6981 (Spannable) mWebTextView.getText(); 6982 Selection.setSelection(pword, start, end); 6983 // If the text entry has created more events, ignore 6984 // this one. 6985 } else if (msg.arg2 == mTextGeneration) { 6986 mWebTextView.setTextAndKeepSelection( 6987 (String) msg.obj); 6988 } 6989 } 6990 break; 6991 case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID: 6992 displaySoftKeyboard(true); 6993 updateTextSelectionFromMessage(msg.arg1, msg.arg2, 6994 (WebViewCore.TextSelectionData) msg.obj); 6995 break; 6996 case UPDATE_TEXT_SELECTION_MSG_ID: 6997 // If no textfield was in focus, and the user touched one, 6998 // causing it to send this message, then WebTextView has not 6999 // been set up yet. Rebuild it so it can set its selection. 7000 rebuildWebTextView(); 7001 updateTextSelectionFromMessage(msg.arg1, msg.arg2, 7002 (WebViewCore.TextSelectionData) msg.obj); 7003 break; 7004 case RETURN_LABEL: 7005 if (inEditingMode() 7006 && mWebTextView.isSameTextField(msg.arg1)) { 7007 mWebTextView.setHint((String) msg.obj); 7008 InputMethodManager imm 7009 = InputMethodManager.peekInstance(); 7010 // The hint is propagated to the IME in 7011 // onCreateInputConnection. If the IME is already 7012 // active, restart it so that its hint text is updated. 7013 if (imm != null && imm.isActive(mWebTextView)) { 7014 imm.restartInput(mWebTextView); 7015 } 7016 } 7017 break; 7018 case MOVE_OUT_OF_PLUGIN: 7019 navHandledKey(msg.arg1, 1, false, 0); 7020 break; 7021 case UPDATE_TEXT_ENTRY_MSG_ID: 7022 // this is sent after finishing resize in WebViewCore. Make 7023 // sure the text edit box is still on the screen. 7024 selectionDone(); 7025 if (inEditingMode() && nativeCursorIsTextInput()) { 7026 mWebTextView.bringIntoView(); 7027 rebuildWebTextView(); 7028 } 7029 break; 7030 case CLEAR_TEXT_ENTRY: 7031 clearTextEntry(false); 7032 break; 7033 case INVAL_RECT_MSG_ID: { 7034 Rect r = (Rect)msg.obj; 7035 if (r == null) { 7036 invalidate(); 7037 } else { 7038 // we need to scale r from content into view coords, 7039 // which viewInvalidate() does for us 7040 viewInvalidate(r.left, r.top, r.right, r.bottom); 7041 } 7042 break; 7043 } 7044 case IMMEDIATE_REPAINT_MSG_ID: { 7045 invalidate(); 7046 break; 7047 } 7048 case SET_ROOT_LAYER_MSG_ID: { 7049 if (0 == msg.arg1) { 7050 // Null indicates deleting the old layer, but 7051 // don't actually do so until we've got the 7052 // new page to display. 7053 mDelayedDeleteRootLayer = true; 7054 } else { 7055 mDelayedDeleteRootLayer = false; 7056 nativeSetRootLayer(msg.arg1); 7057 invalidate(); 7058 } 7059 break; 7060 } 7061 case REQUEST_FORM_DATA: 7062 AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj; 7063 if (mWebTextView.isSameTextField(msg.arg1)) { 7064 mWebTextView.setAdapterCustom(adapter); 7065 } 7066 break; 7067 case RESUME_WEBCORE_PRIORITY: 7068 WebViewCore.resumePriority(); 7069 WebViewCore.resumeUpdatePicture(mWebViewCore); 7070 break; 7071 7072 case LONG_PRESS_CENTER: 7073 // as this is shared by keydown and trackballdown, reset all 7074 // the states 7075 mGotCenterDown = false; 7076 mTrackballDown = false; 7077 performLongClick(); 7078 break; 7079 7080 case WEBCORE_NEED_TOUCH_EVENTS: 7081 mForwardTouchEvents = (msg.arg1 != 0); 7082 break; 7083 7084 case PREVENT_TOUCH_ID: 7085 if (inFullScreenMode()) { 7086 break; 7087 } 7088 if (msg.obj == null) { 7089 if (msg.arg1 == MotionEvent.ACTION_DOWN 7090 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) { 7091 // if prevent default is called from WebCore, UI 7092 // will not handle the rest of the touch events any 7093 // more. 7094 mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES 7095 : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN; 7096 } else if (msg.arg1 == MotionEvent.ACTION_MOVE 7097 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 7098 // the return for the first ACTION_MOVE will decide 7099 // whether UI will handle touch or not. Currently no 7100 // support for alternating prevent default 7101 mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES 7102 : PREVENT_DEFAULT_NO; 7103 } 7104 } else if (msg.arg2 == 0) { 7105 // prevent default is not called in WebCore, so the 7106 // message needs to be reprocessed in UI 7107 TouchEventData ted = (TouchEventData) msg.obj; 7108 switch (ted.mAction) { 7109 case MotionEvent.ACTION_DOWN: 7110 mLastDeferTouchX = ted.mViewX; 7111 mLastDeferTouchY = ted.mViewY; 7112 mDeferTouchMode = TOUCH_INIT_MODE; 7113 break; 7114 case MotionEvent.ACTION_MOVE: { 7115 // no snapping in defer process 7116 if (mDeferTouchMode != TOUCH_DRAG_MODE) { 7117 mDeferTouchMode = TOUCH_DRAG_MODE; 7118 mLastDeferTouchX = ted.mViewX; 7119 mLastDeferTouchY = ted.mViewY; 7120 startDrag(); 7121 } 7122 int deltaX = pinLocX((int) (mScrollX 7123 + mLastDeferTouchX - ted.mViewX)) 7124 - mScrollX; 7125 int deltaY = pinLocY((int) (mScrollY 7126 + mLastDeferTouchY - ted.mViewY)) 7127 - mScrollY; 7128 doDrag(deltaX, deltaY); 7129 if (deltaX != 0) mLastDeferTouchX = ted.mViewX; 7130 if (deltaY != 0) mLastDeferTouchY = ted.mViewY; 7131 break; 7132 } 7133 case MotionEvent.ACTION_UP: 7134 case MotionEvent.ACTION_CANCEL: 7135 if (mDeferTouchMode == TOUCH_DRAG_MODE) { 7136 // no fling in defer process 7137 mScroller.springBack(mScrollX, mScrollY, 0, 7138 computeMaxScrollX(), 0, 7139 computeMaxScrollY()); 7140 invalidate(); 7141 WebViewCore.resumePriority(); 7142 WebViewCore.resumeUpdatePicture(mWebViewCore); 7143 } 7144 mDeferTouchMode = TOUCH_DONE_MODE; 7145 break; 7146 case WebViewCore.ACTION_DOUBLETAP: 7147 // doDoubleTap() needs mLastTouchX/Y as anchor 7148 mLastTouchX = ted.mViewX; 7149 mLastTouchY = ted.mViewY; 7150 doDoubleTap(); 7151 mDeferTouchMode = TOUCH_DONE_MODE; 7152 break; 7153 case WebViewCore.ACTION_LONGPRESS: 7154 HitTestResult hitTest = getHitTestResult(); 7155 if (hitTest != null && hitTest.mType 7156 != HitTestResult.UNKNOWN_TYPE) { 7157 performLongClick(); 7158 rebuildWebTextView(); 7159 } 7160 mDeferTouchMode = TOUCH_DONE_MODE; 7161 break; 7162 } 7163 } 7164 break; 7165 7166 case REQUEST_KEYBOARD: 7167 if (msg.arg1 == 0) { 7168 hideSoftKeyboard(); 7169 } else { 7170 displaySoftKeyboard(false); 7171 } 7172 break; 7173 7174 case FIND_AGAIN: 7175 // Ignore if find has been dismissed. 7176 if (mFindIsUp) { 7177 findAll(mLastFind); 7178 } 7179 break; 7180 7181 case DRAG_HELD_MOTIONLESS: 7182 mHeldMotionless = MOTIONLESS_TRUE; 7183 invalidate(); 7184 // fall through to keep scrollbars awake 7185 7186 case AWAKEN_SCROLL_BARS: 7187 if (mTouchMode == TOUCH_DRAG_MODE 7188 && mHeldMotionless == MOTIONLESS_TRUE) { 7189 awakenScrollBars(ViewConfiguration 7190 .getScrollDefaultDelay(), false); 7191 mPrivateHandler.sendMessageDelayed(mPrivateHandler 7192 .obtainMessage(AWAKEN_SCROLL_BARS), 7193 ViewConfiguration.getScrollDefaultDelay()); 7194 } 7195 break; 7196 7197 case DO_MOTION_UP: 7198 doMotionUp(msg.arg1, msg.arg2); 7199 break; 7200 7201 case SHOW_FULLSCREEN: { 7202 View view = (View) msg.obj; 7203 int npp = msg.arg1; 7204 7205 if (mFullScreenHolder != null) { 7206 Log.w(LOGTAG, "Should not have another full screen."); 7207 mFullScreenHolder.dismiss(); 7208 } 7209 mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp); 7210 mFullScreenHolder.setContentView(view); 7211 mFullScreenHolder.setCancelable(false); 7212 mFullScreenHolder.setCanceledOnTouchOutside(false); 7213 mFullScreenHolder.show(); 7214 7215 break; 7216 } 7217 case HIDE_FULLSCREEN: 7218 if (inFullScreenMode()) { 7219 mFullScreenHolder.dismiss(); 7220 mFullScreenHolder = null; 7221 } 7222 break; 7223 7224 case DOM_FOCUS_CHANGED: 7225 if (inEditingMode()) { 7226 nativeClearCursor(); 7227 rebuildWebTextView(); 7228 } 7229 break; 7230 7231 case SHOW_RECT_MSG_ID: { 7232 WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj; 7233 int x = mScrollX; 7234 int left = contentToViewX(data.mLeft); 7235 int width = contentToViewDimension(data.mWidth); 7236 int maxWidth = contentToViewDimension(data.mContentWidth); 7237 int viewWidth = getViewWidth(); 7238 if (width < viewWidth) { 7239 // center align 7240 x += left + width / 2 - mScrollX - viewWidth / 2; 7241 } else { 7242 x += (int) (left + data.mXPercentInDoc * width 7243 - mScrollX - data.mXPercentInView * viewWidth); 7244 } 7245 if (DebugFlags.WEB_VIEW) { 7246 Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" + 7247 width + ",maxWidth=" + maxWidth + 7248 ",viewWidth=" + viewWidth + ",x=" 7249 + x + ",xPercentInDoc=" + data.mXPercentInDoc + 7250 ",xPercentInView=" + data.mXPercentInView+ ")"); 7251 } 7252 // use the passing content width to cap x as the current 7253 // mContentWidth may not be updated yet 7254 x = Math.max(0, 7255 (Math.min(maxWidth, x + viewWidth)) - viewWidth); 7256 int top = contentToViewY(data.mTop); 7257 int height = contentToViewDimension(data.mHeight); 7258 int maxHeight = contentToViewDimension(data.mContentHeight); 7259 int viewHeight = getViewHeight(); 7260 int y = (int) (top + data.mYPercentInDoc * height - 7261 data.mYPercentInView * viewHeight); 7262 if (DebugFlags.WEB_VIEW) { 7263 Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" + 7264 height + ",maxHeight=" + maxHeight + 7265 ",viewHeight=" + viewHeight + ",y=" 7266 + y + ",yPercentInDoc=" + data.mYPercentInDoc + 7267 ",yPercentInView=" + data.mYPercentInView+ ")"); 7268 } 7269 // use the passing content height to cap y as the current 7270 // mContentHeight may not be updated yet 7271 y = Math.max(0, 7272 (Math.min(maxHeight, y + viewHeight) - viewHeight)); 7273 // We need to take into account the visible title height 7274 // when scrolling since y is an absolute view position. 7275 y = Math.max(0, y - getVisibleTitleHeight()); 7276 scrollTo(x, y); 7277 } 7278 break; 7279 7280 case CENTER_FIT_RECT: 7281 Rect r = (Rect)msg.obj; 7282 mInZoomOverview = false; 7283 centerFitRect(r.left, r.top, r.width(), r.height()); 7284 break; 7285 7286 case SET_SCROLLBAR_MODES: 7287 mHorizontalScrollBarMode = msg.arg1; 7288 mVerticalScrollBarMode = msg.arg2; 7289 break; 7290 7291 default: 7292 super.handleMessage(msg); 7293 break; 7294 } 7295 } 7296 } 7297 7298 /** 7299 * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID 7300 * and UPDATE_TEXT_SELECTION_MSG_ID. Update the selection of WebTextView. 7301 */ 7302 private void updateTextSelectionFromMessage(int nodePointer, 7303 int textGeneration, WebViewCore.TextSelectionData data) { 7304 if (inEditingMode() 7305 && mWebTextView.isSameTextField(nodePointer) 7306 && textGeneration == mTextGeneration) { 7307 mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd); 7308 } 7309 } 7310 7311 // Class used to use a dropdown for a <select> element 7312 private class InvokeListBox implements Runnable { 7313 // Whether the listbox allows multiple selection. 7314 private boolean mMultiple; 7315 // Passed in to a list with multiple selection to tell 7316 // which items are selected. 7317 private int[] mSelectedArray; 7318 // Passed in to a list with single selection to tell 7319 // where the initial selection is. 7320 private int mSelection; 7321 7322 private Container[] mContainers; 7323 7324 // Need these to provide stable ids to my ArrayAdapter, 7325 // which normally does not have stable ids. (Bug 1250098) 7326 private class Container extends Object { 7327 /** 7328 * Possible values for mEnabled. Keep in sync with OptionStatus in 7329 * WebViewCore.cpp 7330 */ 7331 final static int OPTGROUP = -1; 7332 final static int OPTION_DISABLED = 0; 7333 final static int OPTION_ENABLED = 1; 7334 7335 String mString; 7336 int mEnabled; 7337 int mId; 7338 7339 public String toString() { 7340 return mString; 7341 } 7342 } 7343 7344 /** 7345 * Subclass ArrayAdapter so we can disable OptionGroupLabels, 7346 * and allow filtering. 7347 */ 7348 private class MyArrayListAdapter extends ArrayAdapter<Container> { 7349 public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { 7350 super(context, 7351 multiple ? com.android.internal.R.layout.select_dialog_multichoice : 7352 com.android.internal.R.layout.select_dialog_singlechoice, 7353 objects); 7354 } 7355 7356 @Override 7357 public View getView(int position, View convertView, 7358 ViewGroup parent) { 7359 // Always pass in null so that we will get a new CheckedTextView 7360 // Otherwise, an item which was previously used as an <optgroup> 7361 // element (i.e. has no check), could get used as an <option> 7362 // element, which needs a checkbox/radio, but it would not have 7363 // one. 7364 convertView = super.getView(position, null, parent); 7365 Container c = item(position); 7366 if (c != null && Container.OPTION_ENABLED != c.mEnabled) { 7367 // ListView does not draw dividers between disabled and 7368 // enabled elements. Use a LinearLayout to provide dividers 7369 LinearLayout layout = new LinearLayout(mContext); 7370 layout.setOrientation(LinearLayout.VERTICAL); 7371 if (position > 0) { 7372 View dividerTop = new View(mContext); 7373 dividerTop.setBackgroundResource( 7374 android.R.drawable.divider_horizontal_bright); 7375 layout.addView(dividerTop); 7376 } 7377 7378 if (Container.OPTGROUP == c.mEnabled) { 7379 // Currently select_dialog_multichoice and 7380 // select_dialog_singlechoice are CheckedTextViews. If 7381 // that changes, the class cast will no longer be valid. 7382 Assert.assertTrue( 7383 convertView instanceof CheckedTextView); 7384 ((CheckedTextView) convertView).setCheckMarkDrawable( 7385 null); 7386 } else { 7387 // c.mEnabled == Container.OPTION_DISABLED 7388 // Draw the disabled element in a disabled state. 7389 convertView.setEnabled(false); 7390 } 7391 7392 layout.addView(convertView); 7393 if (position < getCount() - 1) { 7394 View dividerBottom = new View(mContext); 7395 dividerBottom.setBackgroundResource( 7396 android.R.drawable.divider_horizontal_bright); 7397 layout.addView(dividerBottom); 7398 } 7399 return layout; 7400 } 7401 return convertView; 7402 } 7403 7404 @Override 7405 public boolean hasStableIds() { 7406 // AdapterView's onChanged method uses this to determine whether 7407 // to restore the old state. Return false so that the old (out 7408 // of date) state does not replace the new, valid state. 7409 return false; 7410 } 7411 7412 private Container item(int position) { 7413 if (position < 0 || position >= getCount()) { 7414 return null; 7415 } 7416 return (Container) getItem(position); 7417 } 7418 7419 @Override 7420 public long getItemId(int position) { 7421 Container item = item(position); 7422 if (item == null) { 7423 return -1; 7424 } 7425 return item.mId; 7426 } 7427 7428 @Override 7429 public boolean areAllItemsEnabled() { 7430 return false; 7431 } 7432 7433 @Override 7434 public boolean isEnabled(int position) { 7435 Container item = item(position); 7436 if (item == null) { 7437 return false; 7438 } 7439 return Container.OPTION_ENABLED == item.mEnabled; 7440 } 7441 } 7442 7443 private InvokeListBox(String[] array, int[] enabled, int[] selected) { 7444 mMultiple = true; 7445 mSelectedArray = selected; 7446 7447 int length = array.length; 7448 mContainers = new Container[length]; 7449 for (int i = 0; i < length; i++) { 7450 mContainers[i] = new Container(); 7451 mContainers[i].mString = array[i]; 7452 mContainers[i].mEnabled = enabled[i]; 7453 mContainers[i].mId = i; 7454 } 7455 } 7456 7457 private InvokeListBox(String[] array, int[] enabled, int selection) { 7458 mSelection = selection; 7459 mMultiple = false; 7460 7461 int length = array.length; 7462 mContainers = new Container[length]; 7463 for (int i = 0; i < length; i++) { 7464 mContainers[i] = new Container(); 7465 mContainers[i].mString = array[i]; 7466 mContainers[i].mEnabled = enabled[i]; 7467 mContainers[i].mId = i; 7468 } 7469 } 7470 7471 /* 7472 * Whenever the data set changes due to filtering, this class ensures 7473 * that the checked item remains checked. 7474 */ 7475 private class SingleDataSetObserver extends DataSetObserver { 7476 private long mCheckedId; 7477 private ListView mListView; 7478 private Adapter mAdapter; 7479 7480 /* 7481 * Create a new observer. 7482 * @param id The ID of the item to keep checked. 7483 * @param l ListView for getting and clearing the checked states 7484 * @param a Adapter for getting the IDs 7485 */ 7486 public SingleDataSetObserver(long id, ListView l, Adapter a) { 7487 mCheckedId = id; 7488 mListView = l; 7489 mAdapter = a; 7490 } 7491 7492 public void onChanged() { 7493 // The filter may have changed which item is checked. Find the 7494 // item that the ListView thinks is checked. 7495 int position = mListView.getCheckedItemPosition(); 7496 long id = mAdapter.getItemId(position); 7497 if (mCheckedId != id) { 7498 // Clear the ListView's idea of the checked item, since 7499 // it is incorrect 7500 mListView.clearChoices(); 7501 // Search for mCheckedId. If it is in the filtered list, 7502 // mark it as checked 7503 int count = mAdapter.getCount(); 7504 for (int i = 0; i < count; i++) { 7505 if (mAdapter.getItemId(i) == mCheckedId) { 7506 mListView.setItemChecked(i, true); 7507 break; 7508 } 7509 } 7510 } 7511 } 7512 7513 public void onInvalidate() {} 7514 } 7515 7516 public void run() { 7517 final ListView listView = (ListView) LayoutInflater.from(mContext) 7518 .inflate(com.android.internal.R.layout.select_dialog, null); 7519 final MyArrayListAdapter adapter = new 7520 MyArrayListAdapter(mContext, mContainers, mMultiple); 7521 AlertDialog.Builder b = new AlertDialog.Builder(mContext) 7522 .setView(listView).setCancelable(true) 7523 .setInverseBackgroundForced(true); 7524 7525 if (mMultiple) { 7526 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 7527 public void onClick(DialogInterface dialog, int which) { 7528 mWebViewCore.sendMessage( 7529 EventHub.LISTBOX_CHOICES, 7530 adapter.getCount(), 0, 7531 listView.getCheckedItemPositions()); 7532 }}); 7533 b.setNegativeButton(android.R.string.cancel, 7534 new DialogInterface.OnClickListener() { 7535 public void onClick(DialogInterface dialog, int which) { 7536 mWebViewCore.sendMessage( 7537 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 7538 }}); 7539 } 7540 final AlertDialog dialog = b.create(); 7541 listView.setAdapter(adapter); 7542 listView.setFocusableInTouchMode(true); 7543 // There is a bug (1250103) where the checks in a ListView with 7544 // multiple items selected are associated with the positions, not 7545 // the ids, so the items do not properly retain their checks when 7546 // filtered. Do not allow filtering on multiple lists until 7547 // that bug is fixed. 7548 7549 listView.setTextFilterEnabled(!mMultiple); 7550 if (mMultiple) { 7551 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 7552 int length = mSelectedArray.length; 7553 for (int i = 0; i < length; i++) { 7554 listView.setItemChecked(mSelectedArray[i], true); 7555 } 7556 } else { 7557 listView.setOnItemClickListener(new OnItemClickListener() { 7558 public void onItemClick(AdapterView parent, View v, 7559 int position, long id) { 7560 mWebViewCore.sendMessage( 7561 EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0); 7562 dialog.dismiss(); 7563 } 7564 }); 7565 if (mSelection != -1) { 7566 listView.setSelection(mSelection); 7567 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 7568 listView.setItemChecked(mSelection, true); 7569 DataSetObserver observer = new SingleDataSetObserver( 7570 adapter.getItemId(mSelection), listView, adapter); 7571 adapter.registerDataSetObserver(observer); 7572 } 7573 } 7574 dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 7575 public void onCancel(DialogInterface dialog) { 7576 mWebViewCore.sendMessage( 7577 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 7578 } 7579 }); 7580 dialog.show(); 7581 } 7582 } 7583 7584 /* 7585 * Request a dropdown menu for a listbox with multiple selection. 7586 * 7587 * @param array Labels for the listbox. 7588 * @param enabledArray State for each element in the list. See static 7589 * integers in Container class. 7590 * @param selectedArray Which positions are initally selected. 7591 */ 7592 void requestListBox(String[] array, int[] enabledArray, int[] 7593 selectedArray) { 7594 mPrivateHandler.post( 7595 new InvokeListBox(array, enabledArray, selectedArray)); 7596 } 7597 7598 private void updateZoomRange(WebViewCore.RestoreState restoreState, 7599 int viewWidth, int minPrefWidth, boolean updateZoomOverview) { 7600 if (restoreState.mMinScale == 0) { 7601 if (restoreState.mMobileSite) { 7602 if (minPrefWidth > Math.max(0, viewWidth)) { 7603 mMinZoomScale = (float) viewWidth / minPrefWidth; 7604 mMinZoomScaleFixed = false; 7605 if (updateZoomOverview) { 7606 WebSettings settings = getSettings(); 7607 mInZoomOverview = settings.getUseWideViewPort() && 7608 settings.getLoadWithOverviewMode(); 7609 } 7610 } else { 7611 mMinZoomScale = restoreState.mDefaultScale; 7612 mMinZoomScaleFixed = true; 7613 } 7614 } else { 7615 mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; 7616 mMinZoomScaleFixed = false; 7617 } 7618 } else { 7619 mMinZoomScale = restoreState.mMinScale; 7620 mMinZoomScaleFixed = true; 7621 } 7622 if (restoreState.mMaxScale == 0) { 7623 mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; 7624 } else { 7625 mMaxZoomScale = restoreState.mMaxScale; 7626 } 7627 } 7628 7629 /* 7630 * Request a dropdown menu for a listbox with single selection or a single 7631 * <select> element. 7632 * 7633 * @param array Labels for the listbox. 7634 * @param enabledArray State for each element in the list. See static 7635 * integers in Container class. 7636 * @param selection Which position is initally selected. 7637 */ 7638 void requestListBox(String[] array, int[] enabledArray, int selection) { 7639 mPrivateHandler.post( 7640 new InvokeListBox(array, enabledArray, selection)); 7641 } 7642 7643 // called by JNI 7644 private void sendMoveFocus(int frame, int node) { 7645 mWebViewCore.sendMessage(EventHub.SET_MOVE_FOCUS, 7646 new WebViewCore.CursorData(frame, node, 0, 0)); 7647 } 7648 7649 // called by JNI 7650 private void sendMoveMouse(int frame, int node, int x, int y) { 7651 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, 7652 new WebViewCore.CursorData(frame, node, x, y)); 7653 } 7654 7655 /* 7656 * Send a mouse move event to the webcore thread. 7657 * 7658 * @param removeFocus Pass true if the "mouse" cursor is now over a node 7659 * which wants key events, but it is not the focus. This 7660 * will make the visual appear as though nothing is in 7661 * focus. Remove the WebTextView, if present, and stop 7662 * drawing the blinking caret. 7663 * called by JNI 7664 */ 7665 private void sendMoveMouseIfLatest(boolean removeFocus) { 7666 if (removeFocus) { 7667 clearTextEntry(true); 7668 } 7669 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, 7670 cursorData()); 7671 } 7672 7673 // called by JNI 7674 private void sendMotionUp(int touchGeneration, 7675 int frame, int node, int x, int y) { 7676 WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); 7677 touchUpData.mMoveGeneration = touchGeneration; 7678 touchUpData.mFrame = frame; 7679 touchUpData.mNode = node; 7680 touchUpData.mX = x; 7681 touchUpData.mY = y; 7682 mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); 7683 } 7684 7685 7686 private int getScaledMaxXScroll() { 7687 int width; 7688 if (mHeightCanMeasure == false) { 7689 width = getViewWidth() / 4; 7690 } else { 7691 Rect visRect = new Rect(); 7692 calcOurVisibleRect(visRect); 7693 width = visRect.width() / 2; 7694 } 7695 // FIXME the divisor should be retrieved from somewhere 7696 return viewToContentX(width); 7697 } 7698 7699 private int getScaledMaxYScroll() { 7700 int height; 7701 if (mHeightCanMeasure == false) { 7702 height = getViewHeight() / 4; 7703 } else { 7704 Rect visRect = new Rect(); 7705 calcOurVisibleRect(visRect); 7706 height = visRect.height() / 2; 7707 } 7708 // FIXME the divisor should be retrieved from somewhere 7709 // the closest thing today is hard-coded into ScrollView.java 7710 // (from ScrollView.java, line 363) int maxJump = height/2; 7711 return Math.round(height * mInvActualScale); 7712 } 7713 7714 /** 7715 * Called by JNI to invalidate view 7716 */ 7717 private void viewInvalidate() { 7718 invalidate(); 7719 } 7720 7721 /** 7722 * Pass the key to the plugin. This assumes that nativeFocusIsPlugin() 7723 * returned true. 7724 */ 7725 private void letPluginHandleNavKey(int keyCode, long time, boolean down) { 7726 int keyEventAction; 7727 int eventHubAction; 7728 if (down) { 7729 keyEventAction = KeyEvent.ACTION_DOWN; 7730 eventHubAction = EventHub.KEY_DOWN; 7731 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 7732 } else { 7733 keyEventAction = KeyEvent.ACTION_UP; 7734 eventHubAction = EventHub.KEY_UP; 7735 } 7736 KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode, 7737 1, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0) 7738 | (false ? KeyEvent.META_ALT_ON : 0) // FIXME 7739 | (false ? KeyEvent.META_SYM_ON : 0) // FIXME 7740 , 0, 0, 0); 7741 mWebViewCore.sendMessage(eventHubAction, event); 7742 } 7743 7744 // return true if the key was handled 7745 private boolean navHandledKey(int keyCode, int count, boolean noScroll, 7746 long time) { 7747 if (mNativeClass == 0) { 7748 return false; 7749 } 7750 mLastCursorTime = time; 7751 mLastCursorBounds = nativeGetCursorRingBounds(); 7752 boolean keyHandled 7753 = nativeMoveCursor(keyCode, count, noScroll) == false; 7754 if (DebugFlags.WEB_VIEW) { 7755 Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds 7756 + " mLastCursorTime=" + mLastCursorTime 7757 + " handled=" + keyHandled); 7758 } 7759 if (keyHandled == false || mHeightCanMeasure == false) { 7760 return keyHandled; 7761 } 7762 Rect contentCursorRingBounds = nativeGetCursorRingBounds(); 7763 if (contentCursorRingBounds.isEmpty()) return keyHandled; 7764 Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds); 7765 Rect visRect = new Rect(); 7766 calcOurVisibleRect(visRect); 7767 Rect outset = new Rect(visRect); 7768 int maxXScroll = visRect.width() / 2; 7769 int maxYScroll = visRect.height() / 2; 7770 outset.inset(-maxXScroll, -maxYScroll); 7771 if (Rect.intersects(outset, viewCursorRingBounds) == false) { 7772 return keyHandled; 7773 } 7774 // FIXME: Necessary because ScrollView/ListView do not scroll left/right 7775 int maxH = Math.min(viewCursorRingBounds.right - visRect.right, 7776 maxXScroll); 7777 if (maxH > 0) { 7778 pinScrollBy(maxH, 0, true, 0); 7779 } else { 7780 maxH = Math.max(viewCursorRingBounds.left - visRect.left, 7781 -maxXScroll); 7782 if (maxH < 0) { 7783 pinScrollBy(maxH, 0, true, 0); 7784 } 7785 } 7786 if (mLastCursorBounds.isEmpty()) return keyHandled; 7787 if (mLastCursorBounds.equals(contentCursorRingBounds)) { 7788 return keyHandled; 7789 } 7790 if (DebugFlags.WEB_VIEW) { 7791 Log.v(LOGTAG, "navHandledKey contentCursorRingBounds=" 7792 + contentCursorRingBounds); 7793 } 7794 requestRectangleOnScreen(viewCursorRingBounds); 7795 mUserScroll = true; 7796 return keyHandled; 7797 } 7798 7799 /** 7800 * Set the background color. It's white by default. Pass 7801 * zero to make the view transparent. 7802 * @param color the ARGB color described by Color.java 7803 */ 7804 public void setBackgroundColor(int color) { 7805 mBackgroundColor = color; 7806 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color); 7807 } 7808 7809 public void debugDump() { 7810 nativeDebugDump(); 7811 mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); 7812 } 7813 7814 /** 7815 * Draw the HTML page into the specified canvas. This call ignores any 7816 * view-specific zoom, scroll offset, or other changes. It does not draw 7817 * any view-specific chrome, such as progress or URL bars. 7818 * 7819 * @hide only needs to be accessible to Browser and testing 7820 */ 7821 public void drawPage(Canvas canvas) { 7822 mWebViewCore.drawContentPicture(canvas, 0, false, false); 7823 } 7824 7825 /** 7826 * Set the time to wait between passing touches to WebCore. See also the 7827 * TOUCH_SENT_INTERVAL member for further discussion. 7828 * 7829 * @hide This is only used by the DRT test application. 7830 */ 7831 public void setTouchInterval(int interval) { 7832 mCurrentTouchInterval = interval; 7833 } 7834 7835 /** 7836 * Update our cache with updatedText. 7837 * @param updatedText The new text to put in our cache. 7838 */ 7839 /* package */ void updateCachedTextfield(String updatedText) { 7840 // Also place our generation number so that when we look at the cache 7841 // we recognize that it is up to date. 7842 nativeUpdateCachedTextfield(updatedText, mTextGeneration); 7843 } 7844 7845 private native int nativeCacheHitFramePointer(); 7846 private native Rect nativeCacheHitNodeBounds(); 7847 private native int nativeCacheHitNodePointer(); 7848 /* package */ native void nativeClearCursor(); 7849 private native void nativeCreate(int ptr); 7850 private native int nativeCursorFramePointer(); 7851 private native Rect nativeCursorNodeBounds(); 7852 private native int nativeCursorNodePointer(); 7853 /* package */ native boolean nativeCursorMatchesFocus(); 7854 private native boolean nativeCursorIntersects(Rect visibleRect); 7855 private native boolean nativeCursorIsAnchor(); 7856 private native boolean nativeCursorIsTextInput(); 7857 private native Point nativeCursorPosition(); 7858 private native String nativeCursorText(); 7859 /** 7860 * Returns true if the native cursor node says it wants to handle key events 7861 * (ala plugins). This can only be called if mNativeClass is non-zero! 7862 */ 7863 private native boolean nativeCursorWantsKeyEvents(); 7864 private native void nativeDebugDump(); 7865 private native void nativeDestroy(); 7866 private native boolean nativeEvaluateLayersAnimations(); 7867 private native void nativeExtendSelection(int x, int y); 7868 private native void nativeDrawExtras(Canvas canvas, int extra); 7869 private native void nativeDumpDisplayTree(String urlOrNull); 7870 private native int nativeFindAll(String findLower, String findUpper); 7871 private native void nativeFindNext(boolean forward); 7872 /* package */ native int nativeFocusCandidateFramePointer(); 7873 /* package */ native boolean nativeFocusCandidateHasNextTextfield(); 7874 /* package */ native boolean nativeFocusCandidateIsPassword(); 7875 private native boolean nativeFocusCandidateIsRtlText(); 7876 private native boolean nativeFocusCandidateIsTextInput(); 7877 /* package */ native int nativeFocusCandidateMaxLength(); 7878 /* package */ native String nativeFocusCandidateName(); 7879 private native Rect nativeFocusCandidateNodeBounds(); 7880 /** 7881 * @return A Rect with left, top, right, bottom set to the corresponding 7882 * padding values in the focus candidate, if it is a textfield/textarea with 7883 * a style. Otherwise return null. This is not actually a rectangle; Rect 7884 * is being used to pass four integers. 7885 */ 7886 private native Rect nativeFocusCandidatePaddingRect(); 7887 /* package */ native int nativeFocusCandidatePointer(); 7888 private native String nativeFocusCandidateText(); 7889 private native int nativeFocusCandidateTextSize(); 7890 /** 7891 * Returns an integer corresponding to WebView.cpp::type. 7892 * See WebTextView.setType() 7893 */ 7894 private native int nativeFocusCandidateType(); 7895 private native boolean nativeFocusIsPlugin(); 7896 private native Rect nativeFocusNodeBounds(); 7897 /* package */ native int nativeFocusNodePointer(); 7898 private native Rect nativeGetCursorRingBounds(); 7899 private native String nativeGetSelection(); 7900 private native boolean nativeHasCursorNode(); 7901 private native boolean nativeHasFocusNode(); 7902 private native void nativeHideCursor(); 7903 private native boolean nativeHitSelection(int x, int y); 7904 private native String nativeImageURI(int x, int y); 7905 private native void nativeInstrumentReport(); 7906 /* package */ native boolean nativeMoveCursorToNextTextInput(); 7907 // return true if the page has been scrolled 7908 private native boolean nativeMotionUp(int x, int y, int slop); 7909 // returns false if it handled the key 7910 private native boolean nativeMoveCursor(int keyCode, int count, 7911 boolean noScroll); 7912 private native int nativeMoveGeneration(); 7913 private native void nativeMoveSelection(int x, int y); 7914 private native boolean nativePointInNavCache(int x, int y, int slop); 7915 // Like many other of our native methods, you must make sure that 7916 // mNativeClass is not null before calling this method. 7917 private native void nativeRecordButtons(boolean focused, 7918 boolean pressed, boolean invalidate); 7919 private native void nativeResetSelection(); 7920 private native void nativeSelectAll(); 7921 private native void nativeSelectBestAt(Rect rect); 7922 private native int nativeSelectionX(); 7923 private native int nativeSelectionY(); 7924 private native int nativeFindIndex(); 7925 private native void nativeSetExtendSelection(); 7926 private native void nativeSetFindIsEmpty(); 7927 private native void nativeSetFindIsUp(boolean isUp); 7928 private native void nativeSetFollowedLink(boolean followed); 7929 private native void nativeSetHeightCanMeasure(boolean measure); 7930 private native void nativeSetRootLayer(int layer); 7931 private native void nativeSetSelectionPointer(boolean set, 7932 float scale, int x, int y); 7933 private native boolean nativeStartSelection(int x, int y); 7934 private native void nativeSetSelectionRegion(boolean set); 7935 private native Rect nativeSubtractLayers(Rect content); 7936 private native int nativeTextGeneration(); 7937 // Never call this version except by updateCachedTextfield(String) - 7938 // we always want to pass in our generation number. 7939 private native void nativeUpdateCachedTextfield(String updatedText, 7940 int generation); 7941 private native boolean nativeWordSelection(int x, int y); 7942 // return NO_LEFTEDGE means failure. 7943 private static final int NO_LEFTEDGE = -1; 7944 private native int nativeGetBlockLeftEdge(int x, int y, float scale); 7945} 7946