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