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