WebView.java revision 83d4ba83ddf8309d2c0c38d69154217d6a9c0a6c
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.app.AlertDialog; 20import android.content.Context; 21import android.content.DialogInterface; 22import android.content.Intent; 23import android.content.DialogInterface.OnCancelListener; 24import android.database.DataSetObserver; 25import android.graphics.Bitmap; 26import android.graphics.Canvas; 27import android.graphics.Color; 28import android.graphics.Picture; 29import android.graphics.Point; 30import android.graphics.Rect; 31import android.graphics.Region; 32import android.graphics.drawable.Drawable; 33import android.net.http.SslCertificate; 34import android.net.Uri; 35import android.os.Bundle; 36import android.os.Handler; 37import android.os.Message; 38import android.os.ServiceManager; 39import android.os.SystemClock; 40import android.provider.Checkin; 41import android.text.IClipboard; 42import android.text.Selection; 43import android.text.Spannable; 44import android.util.AttributeSet; 45import android.util.EventLog; 46import android.util.Log; 47import android.util.TypedValue; 48import android.view.Gravity; 49import android.view.KeyEvent; 50import android.view.LayoutInflater; 51import android.view.MotionEvent; 52import android.view.SoundEffectConstants; 53import android.view.VelocityTracker; 54import android.view.View; 55import android.view.ViewConfiguration; 56import android.view.ViewGroup; 57import android.view.ViewParent; 58import android.view.ViewTreeObserver; 59import android.view.animation.AlphaAnimation; 60import android.view.inputmethod.InputMethodManager; 61import android.webkit.WebTextView.AutoCompleteAdapter; 62import android.webkit.WebViewCore.EventHub; 63import android.widget.AbsoluteLayout; 64import android.widget.Adapter; 65import android.widget.AdapterView; 66import android.widget.ArrayAdapter; 67import android.widget.FrameLayout; 68import android.widget.ListView; 69import android.widget.Scroller; 70import android.widget.Toast; 71import android.widget.ZoomButtonsController; 72import android.widget.ZoomControls; 73import android.widget.AdapterView.OnItemClickListener; 74 75import java.io.File; 76import java.io.FileInputStream; 77import java.io.FileNotFoundException; 78import java.io.FileOutputStream; 79import java.io.IOException; 80import java.net.URLDecoder; 81import java.util.ArrayList; 82import java.util.List; 83import java.util.Map; 84 85/** 86 * <p>A View that displays web pages. This class is the basis upon which you 87 * can roll your own web browser or simply display some online content within your Activity. 88 * It uses the WebKit rendering engine to display 89 * web pages and includes methods to navigate forward and backward 90 * through a history, zoom in and out, perform text searches and more.</p> 91 * <p>To enable the built-in zoom, set 92 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} 93 * (introduced in API version 3). 94 * <p>Note that, in order for your Activity to access the Internet and load web pages 95 * in a WebView, you must add the <var>INTERNET</var> permissions to your 96 * Android Manifest file:</p> 97 * <pre><uses-permission android:name="android.permission.INTERNET" /></pre> 98 * 99 * <p>This must be a child of the <code><manifest></code> element.</p> 100 * 101 * <h3>Basic usage</h3> 102 * 103 * <p>By default, a WebView provides no browser-like widgets, does not 104 * enable JavaScript and errors will be ignored. If your goal is only 105 * to display some HTML as a part of your UI, this is probably fine; 106 * the user won't need to interact with the web page beyond reading 107 * it, and the web page won't need to interact with the user. If you 108 * actually want a fully blown web browser, then you probably want to 109 * invoke the Browser application with your URL rather than show it 110 * with a WebView. See {@link android.content.Intent} for more information.</p> 111 * 112 * <pre class="prettyprint"> 113 * WebView webview = new WebView(this); 114 * setContentView(webview); 115 * 116 * // Simplest usage: note that an exception will NOT be thrown 117 * // if there is an error loading this page (see below). 118 * webview.loadUrl("http://slashdot.org/"); 119 * 120 * // Of course you can also load from any string: 121 * String summary = "<html><body>You scored <b>192</b> points.</body></html>"; 122 * webview.loadData(summary, "text/html", "utf-8"); 123 * // ... although note that there are restrictions on what this HTML can do. 124 * // See the JavaDocs for loadData and loadDataWithBaseUrl for more info. 125 * </pre> 126 * 127 * <p>A WebView has several customization points where you can add your 128 * own behavior. These are:</p> 129 * 130 * <ul> 131 * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass. 132 * This class is called when something that might impact a 133 * browser UI happens, for instance, progress updates and 134 * JavaScript alerts are sent here. 135 * </li> 136 * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. 137 * It will be called when things happen that impact the 138 * rendering of the content, eg, errors or form submissions. You 139 * can also intercept URL loading here.</li> 140 * <li>Via the {@link android.webkit.WebSettings} class, which contains 141 * miscellaneous configuration. </li> 142 * <li>With the {@link android.webkit.WebView#addJavascriptInterface} method. 143 * This lets you bind Java objects into the WebView so they can be 144 * controlled from the web pages JavaScript.</li> 145 * </ul> 146 * 147 * <p>Here's a more complicated example, showing error handling, 148 * settings, and progress notification:</p> 149 * 150 * <pre class="prettyprint"> 151 * // Let's display the progress in the activity title bar, like the 152 * // browser app does. 153 * getWindow().requestFeature(Window.FEATURE_PROGRESS); 154 * 155 * webview.getSettings().setJavaScriptEnabled(true); 156 * 157 * final Activity activity = this; 158 * webview.setWebChromeClient(new WebChromeClient() { 159 * public void onProgressChanged(WebView view, int progress) { 160 * // Activities and WebViews measure progress with different scales. 161 * // The progress meter will automatically disappear when we reach 100% 162 * activity.setProgress(progress * 1000); 163 * } 164 * }); 165 * webview.setWebViewClient(new WebViewClient() { 166 * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 167 * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show(); 168 * } 169 * }); 170 * 171 * webview.loadUrl("http://slashdot.org/"); 172 * </pre> 173 * 174 * <h3>Cookie and window management</h3> 175 * 176 * <p>For obvious security reasons, your application has its own 177 * cache, cookie store etc - it does not share the Browser 178 * applications data. Cookies are managed on a separate thread, so 179 * operations like index building don't block the UI 180 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager} 181 * if you want to use cookies in your application. 182 * </p> 183 * 184 * <p>By default, requests by the HTML to open new windows are 185 * ignored. This is true whether they be opened by JavaScript or by 186 * the target attribute on a link. You can customize your 187 * WebChromeClient to provide your own behaviour for opening multiple windows, 188 * and render them in whatever manner you want.</p> 189 * 190 * <p>Standard behavior for an Activity is to be destroyed and 191 * recreated when the devices orientation is changed. This will cause 192 * the WebView to reload the current page. If you don't want that, you 193 * can set your Activity to handle the orientation and keyboardHidden 194 * changes, and then just leave the WebView alone. It'll automatically 195 * re-orient itself as appropriate.</p> 196 */ 197public class WebView extends AbsoluteLayout 198 implements ViewTreeObserver.OnGlobalFocusChangeListener, 199 ViewGroup.OnHierarchyChangeListener { 200 201 // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing 202 // the screen all-the-time. Good for profiling our drawing code 203 static private final boolean AUTO_REDRAW_HACK = false; 204 // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK 205 private boolean mAutoRedraw; 206 207 static final String LOGTAG = "webview"; 208 209 private static class ExtendedZoomControls extends FrameLayout { 210 public ExtendedZoomControls(Context context, AttributeSet attrs) { 211 super(context, attrs); 212 LayoutInflater inflater = (LayoutInflater) 213 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 214 inflater.inflate(com.android.internal.R.layout.zoom_magnify, this, true); 215 mPlusMinusZoomControls = (ZoomControls) findViewById( 216 com.android.internal.R.id.zoomControls); 217 findViewById(com.android.internal.R.id.zoomMagnify).setVisibility( 218 View.GONE); 219 } 220 221 public void show(boolean showZoom, boolean canZoomOut) { 222 mPlusMinusZoomControls.setVisibility( 223 showZoom ? View.VISIBLE : View.GONE); 224 fade(View.VISIBLE, 0.0f, 1.0f); 225 } 226 227 public void hide() { 228 fade(View.GONE, 1.0f, 0.0f); 229 } 230 231 private void fade(int visibility, float startAlpha, float endAlpha) { 232 AlphaAnimation anim = new AlphaAnimation(startAlpha, endAlpha); 233 anim.setDuration(500); 234 startAnimation(anim); 235 setVisibility(visibility); 236 } 237 238 public boolean hasFocus() { 239 return mPlusMinusZoomControls.hasFocus(); 240 } 241 242 public void setOnZoomInClickListener(OnClickListener listener) { 243 mPlusMinusZoomControls.setOnZoomInClickListener(listener); 244 } 245 246 public void setOnZoomOutClickListener(OnClickListener listener) { 247 mPlusMinusZoomControls.setOnZoomOutClickListener(listener); 248 } 249 250 ZoomControls mPlusMinusZoomControls; 251 } 252 253 /** 254 * Transportation object for returning WebView across thread boundaries. 255 */ 256 public class WebViewTransport { 257 private WebView mWebview; 258 259 /** 260 * Set the WebView to the transportation object. 261 * @param webview The WebView to transport. 262 */ 263 public synchronized void setWebView(WebView webview) { 264 mWebview = webview; 265 } 266 267 /** 268 * Return the WebView object. 269 * @return WebView The transported WebView object. 270 */ 271 public synchronized WebView getWebView() { 272 return mWebview; 273 } 274 } 275 276 // A final CallbackProxy shared by WebViewCore and BrowserFrame. 277 private final CallbackProxy mCallbackProxy; 278 279 private final WebViewDatabase mDatabase; 280 281 // SSL certificate for the main top-level page (if secure) 282 private SslCertificate mCertificate; 283 284 // Native WebView pointer that is 0 until the native object has been 285 // created. 286 private int mNativeClass; 287 // This would be final but it needs to be set to null when the WebView is 288 // destroyed. 289 private WebViewCore mWebViewCore; 290 // Handler for dispatching UI messages. 291 /* package */ final Handler mPrivateHandler = new PrivateHandler(); 292 private WebTextView mWebTextView; 293 // Used to ignore changes to webkit text that arrives to the UI side after 294 // more key events. 295 private int mTextGeneration; 296 297 // Used by WebViewCore to create child views. 298 /* package */ final ViewManager mViewManager; 299 300 /** 301 * Position of the last touch event. 302 */ 303 private float mLastTouchX; 304 private float mLastTouchY; 305 306 /** 307 * Time of the last touch event. 308 */ 309 private long mLastTouchTime; 310 311 /** 312 * Time of the last time sending touch event to WebViewCore 313 */ 314 private long mLastSentTouchTime; 315 316 /** 317 * The minimum elapsed time before sending another ACTION_MOVE event to 318 * WebViewCore. This really should be tuned for each type of the devices. 319 * For example in Google Map api test case, it takes Dream device at least 320 * 150ms to do a full cycle in the WebViewCore by processing a touch event, 321 * triggering the layout and drawing the picture. While the same process 322 * takes 60+ms on the current high speed device. If we make 323 * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent 324 * to WebViewCore queue and the real layout and draw events will be pushed 325 * to further, which slows down the refresh rate. Choose 50 to favor the 326 * current high speed devices. For Dream like devices, 100 is a better 327 * choice. Maybe make this in the buildspec later. 328 */ 329 private static final int TOUCH_SENT_INTERVAL = 50; 330 331 /** 332 * Helper class to get velocity for fling 333 */ 334 VelocityTracker mVelocityTracker; 335 private int mMaximumFling; 336 private float mLastVelocity; 337 private float mLastVelX; 338 private float mLastVelY; 339 340 /** 341 * Touch mode 342 */ 343 private int mTouchMode = TOUCH_DONE_MODE; 344 private static final int TOUCH_INIT_MODE = 1; 345 private static final int TOUCH_DRAG_START_MODE = 2; 346 private static final int TOUCH_DRAG_MODE = 3; 347 private static final int TOUCH_SHORTPRESS_START_MODE = 4; 348 private static final int TOUCH_SHORTPRESS_MODE = 5; 349 private static final int TOUCH_DOUBLE_TAP_MODE = 6; 350 private static final int TOUCH_DONE_MODE = 7; 351 private static final int TOUCH_SELECT_MODE = 8; 352 353 // Whether to forward the touch events to WebCore 354 private boolean mForwardTouchEvents = false; 355 356 // Whether to prevent drag during touch. The initial value depends on 357 // mForwardTouchEvents. If WebCore wants touch events, we assume it will 358 // take control of touch events unless it says no for touch down event. 359 private boolean mPreventDrag; 360 361 // To keep track of whether the current drag was initiated by a WebTextView, 362 // so that we know not to hide the cursor 363 boolean mDragFromTextInput; 364 365 // Whether or not to draw the cursor ring. 366 private boolean mDrawCursorRing = true; 367 368 // true if onPause has been called (and not onResume) 369 private boolean mIsPaused; 370 371 /** 372 * Customizable constant 373 */ 374 // pre-computed square of ViewConfiguration.getScaledTouchSlop() 375 private int mTouchSlopSquare; 376 // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop() 377 private int mDoubleTapSlopSquare; 378 // pre-computed density adjusted navigation slop 379 private int mNavSlop; 380 // This should be ViewConfiguration.getTapTimeout() 381 // But system time out is 100ms, which is too short for the browser. 382 // In the browser, if it switches out of tap too soon, jump tap won't work. 383 private static final int TAP_TIMEOUT = 200; 384 // This should be ViewConfiguration.getLongPressTimeout() 385 // But system time out is 500ms, which is too short for the browser. 386 // With a short timeout, it's difficult to treat trigger a short press. 387 private static final int LONG_PRESS_TIMEOUT = 1000; 388 // needed to avoid flinging after a pause of no movement 389 private static final int MIN_FLING_TIME = 250; 390 // The time that the Zoom Controls are visible before fading away 391 private static final long ZOOM_CONTROLS_TIMEOUT = 392 ViewConfiguration.getZoomControlsTimeout(); 393 // The amount of content to overlap between two screens when going through 394 // pages with the space bar, in pixels. 395 private static final int PAGE_SCROLL_OVERLAP = 24; 396 397 /** 398 * These prevent calling requestLayout if either dimension is fixed. This 399 * depends on the layout parameters and the measure specs. 400 */ 401 boolean mWidthCanMeasure; 402 boolean mHeightCanMeasure; 403 404 // Remember the last dimensions we sent to the native side so we can avoid 405 // sending the same dimensions more than once. 406 int mLastWidthSent; 407 int mLastHeightSent; 408 409 private int mContentWidth; // cache of value from WebViewCore 410 private int mContentHeight; // cache of value from WebViewCore 411 412 // Need to have the separate control for horizontal and vertical scrollbar 413 // style than the View's single scrollbar style 414 private boolean mOverlayHorizontalScrollbar = true; 415 private boolean mOverlayVerticalScrollbar = false; 416 417 // our standard speed. this way small distances will be traversed in less 418 // time than large distances, but we cap the duration, so that very large 419 // distances won't take too long to get there. 420 private static final int STD_SPEED = 480; // pixels per second 421 // time for the longest scroll animation 422 private static final int MAX_DURATION = 750; // milliseconds 423 private Scroller mScroller; 424 425 private boolean mWrapContent; 426 427 /** 428 * Private message ids 429 */ 430 private static final int REMEMBER_PASSWORD = 1; 431 private static final int NEVER_REMEMBER_PASSWORD = 2; 432 private static final int SWITCH_TO_SHORTPRESS = 3; 433 private static final int SWITCH_TO_LONGPRESS = 4; 434 private static final int RELEASE_SINGLE_TAP = 5; 435 private static final int REQUEST_FORM_DATA = 6; 436 private static final int RESUME_WEBCORE_UPDATE = 7; 437 438 //! arg1=x, arg2=y 439 static final int SCROLL_TO_MSG_ID = 10; 440 static final int SCROLL_BY_MSG_ID = 11; 441 //! arg1=x, arg2=y 442 static final int SPAWN_SCROLL_TO_MSG_ID = 12; 443 //! arg1=x, arg2=y 444 static final int SYNC_SCROLL_TO_MSG_ID = 13; 445 static final int NEW_PICTURE_MSG_ID = 14; 446 static final int UPDATE_TEXT_ENTRY_MSG_ID = 15; 447 static final int WEBCORE_INITIALIZED_MSG_ID = 16; 448 static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 17; 449 static final int MOVE_OUT_OF_PLUGIN = 19; 450 static final int CLEAR_TEXT_ENTRY = 20; 451 static final int UPDATE_TEXT_SELECTION_MSG_ID = 21; 452 static final int UPDATE_CLIPBOARD = 22; 453 static final int LONG_PRESS_CENTER = 23; 454 static final int PREVENT_TOUCH_ID = 24; 455 static final int WEBCORE_NEED_TOUCH_EVENTS = 25; 456 // obj=Rect in doc coordinates 457 static final int INVAL_RECT_MSG_ID = 26; 458 static final int REQUEST_KEYBOARD = 27; 459 460 static final String[] HandlerDebugString = { 461 "REMEMBER_PASSWORD", // = 1; 462 "NEVER_REMEMBER_PASSWORD", // = 2; 463 "SWITCH_TO_SHORTPRESS", // = 3; 464 "SWITCH_TO_LONGPRESS", // = 4; 465 "RELEASE_SINGLE_TAP", // = 5; 466 "REQUEST_FORM_DATA", // = 6; 467 "SWITCH_TO_CLICK", // = 7; 468 "RESUME_WEBCORE_UPDATE", // = 8; 469 "9", 470 "SCROLL_TO_MSG_ID", // = 10; 471 "SCROLL_BY_MSG_ID", // = 11; 472 "SPAWN_SCROLL_TO_MSG_ID", // = 12; 473 "SYNC_SCROLL_TO_MSG_ID", // = 13; 474 "NEW_PICTURE_MSG_ID", // = 14; 475 "UPDATE_TEXT_ENTRY_MSG_ID", // = 15; 476 "WEBCORE_INITIALIZED_MSG_ID", // = 16; 477 "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 17; 478 "18", // = 18; 479 "MOVE_OUT_OF_PLUGIN", // = 19; 480 "CLEAR_TEXT_ENTRY", // = 20; 481 "UPDATE_TEXT_SELECTION_MSG_ID", // = 21; 482 "UPDATE_CLIPBOARD", // = 22; 483 "LONG_PRESS_CENTER", // = 23; 484 "PREVENT_TOUCH_ID", // = 24; 485 "WEBCORE_NEED_TOUCH_EVENTS", // = 25; 486 "INVAL_RECT_MSG_ID", // = 26; 487 "REQUEST_KEYBOARD" // = 27; 488 }; 489 490 // default scale limit. Depending on the display density 491 private static float DEFAULT_MAX_ZOOM_SCALE; 492 private static float DEFAULT_MIN_ZOOM_SCALE; 493 // scale limit, which can be set through viewport meta tag in the web page 494 private float mMaxZoomScale; 495 private float mMinZoomScale; 496 private boolean mMinZoomScaleFixed = true; 497 498 // initial scale in percent. 0 means using default. 499 private int mInitialScale = 0; 500 501 // while in the zoom overview mode, the page's width is fully fit to the 502 // current window. The page is alive, in another words, you can click to 503 // follow the links. Double tap will toggle between zoom overview mode and 504 // the last zoom scale. 505 boolean mInZoomOverview = false; 506 507 // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn, 508 // engadget always have wider mContentWidth no matter what viewport size is. 509 int mZoomOverviewWidth = WebViewCore.DEFAULT_VIEWPORT_WIDTH; 510 float mLastScale; 511 512 // default scale. Depending on the display density. 513 static int DEFAULT_SCALE_PERCENT; 514 private float mDefaultScale; 515 516 // set to true temporarily while the zoom control is being dragged 517 private boolean mPreviewZoomOnly = false; 518 519 // computed scale and inverse, from mZoomWidth. 520 private float mActualScale; 521 private float mInvActualScale; 522 // if this is non-zero, it is used on drawing rather than mActualScale 523 private float mZoomScale; 524 private float mInvInitialZoomScale; 525 private float mInvFinalZoomScale; 526 private int mInitialScrollX; 527 private int mInitialScrollY; 528 private long mZoomStart; 529 private static final int ZOOM_ANIMATION_LENGTH = 500; 530 531 private boolean mUserScroll = false; 532 533 private int mSnapScrollMode = SNAP_NONE; 534 private static final int SNAP_NONE = 1; 535 private static final int SNAP_X = 2; 536 private static final int SNAP_Y = 3; 537 private static final int SNAP_X_LOCK = 4; 538 private static final int SNAP_Y_LOCK = 5; 539 private boolean mSnapPositive; 540 541 // Used to match key downs and key ups 542 private boolean mGotKeyDown; 543 544 /* package */ static boolean mLogEvent = true; 545 private static final int EVENT_LOG_ZOOM_LEVEL_CHANGE = 70101; 546 private static final int EVENT_LOG_DOUBLE_TAP_DURATION = 70102; 547 548 // for event log 549 private long mLastTouchUpTime = 0; 550 551 /** 552 * URI scheme for telephone number 553 */ 554 public static final String SCHEME_TEL = "tel:"; 555 /** 556 * URI scheme for email address 557 */ 558 public static final String SCHEME_MAILTO = "mailto:"; 559 /** 560 * URI scheme for map address 561 */ 562 public static final String SCHEME_GEO = "geo:0,0?q="; 563 564 private int mBackgroundColor = Color.WHITE; 565 566 // Used to notify listeners of a new picture. 567 private PictureListener mPictureListener; 568 /** 569 * Interface to listen for new pictures as they change. 570 */ 571 public interface PictureListener { 572 /** 573 * Notify the listener that the picture has changed. 574 * @param view The WebView that owns the picture. 575 * @param picture The new picture. 576 */ 577 public void onNewPicture(WebView view, Picture picture); 578 } 579 580 // FIXME: Want to make this public, but need to change the API file. 581 public /*static*/ class HitTestResult { 582 /** 583 * Default HitTestResult, where the target is unknown 584 */ 585 public static final int UNKNOWN_TYPE = 0; 586 /** 587 * HitTestResult for hitting a HTML::a tag 588 */ 589 public static final int ANCHOR_TYPE = 1; 590 /** 591 * HitTestResult for hitting a phone number 592 */ 593 public static final int PHONE_TYPE = 2; 594 /** 595 * HitTestResult for hitting a map address 596 */ 597 public static final int GEO_TYPE = 3; 598 /** 599 * HitTestResult for hitting an email address 600 */ 601 public static final int EMAIL_TYPE = 4; 602 /** 603 * HitTestResult for hitting an HTML::img tag 604 */ 605 public static final int IMAGE_TYPE = 5; 606 /** 607 * HitTestResult for hitting a HTML::a tag which contains HTML::img 608 */ 609 public static final int IMAGE_ANCHOR_TYPE = 6; 610 /** 611 * HitTestResult for hitting a HTML::a tag with src=http 612 */ 613 public static final int SRC_ANCHOR_TYPE = 7; 614 /** 615 * HitTestResult for hitting a HTML::a tag with src=http + HTML::img 616 */ 617 public static final int SRC_IMAGE_ANCHOR_TYPE = 8; 618 /** 619 * HitTestResult for hitting an edit text area 620 */ 621 public static final int EDIT_TEXT_TYPE = 9; 622 623 private int mType; 624 private String mExtra; 625 626 HitTestResult() { 627 mType = UNKNOWN_TYPE; 628 } 629 630 private void setType(int type) { 631 mType = type; 632 } 633 634 private void setExtra(String extra) { 635 mExtra = extra; 636 } 637 638 public int getType() { 639 return mType; 640 } 641 642 public String getExtra() { 643 return mExtra; 644 } 645 } 646 647 // The View containing the zoom controls 648 private ExtendedZoomControls mZoomControls; 649 private Runnable mZoomControlRunnable; 650 651 private ZoomButtonsController mZoomButtonsController; 652 653 // These keep track of the center point of the zoom. They are used to 654 // determine the point around which we should zoom. 655 private float mZoomCenterX; 656 private float mZoomCenterY; 657 658 private ZoomButtonsController.OnZoomListener mZoomListener = 659 new ZoomButtonsController.OnZoomListener() { 660 661 public void onVisibilityChanged(boolean visible) { 662 if (visible) { 663 switchOutDrawHistory(); 664 updateZoomButtonsEnabled(); 665 } 666 } 667 668 public void onZoom(boolean zoomIn) { 669 if (zoomIn) { 670 zoomIn(); 671 } else { 672 zoomOut(); 673 } 674 675 updateZoomButtonsEnabled(); 676 } 677 }; 678 679 /** 680 * Construct a new WebView with a Context object. 681 * @param context A Context object used to access application assets. 682 */ 683 public WebView(Context context) { 684 this(context, null); 685 } 686 687 /** 688 * Construct a new WebView with layout parameters. 689 * @param context A Context object used to access application assets. 690 * @param attrs An AttributeSet passed to our parent. 691 */ 692 public WebView(Context context, AttributeSet attrs) { 693 this(context, attrs, com.android.internal.R.attr.webViewStyle); 694 } 695 696 /** 697 * Construct a new WebView with layout parameters and a default style. 698 * @param context A Context object used to access application assets. 699 * @param attrs An AttributeSet passed to our parent. 700 * @param defStyle The default style resource ID. 701 */ 702 public WebView(Context context, AttributeSet attrs, int defStyle) { 703 this(context, attrs, defStyle, null); 704 } 705 706 /** 707 * Construct a new WebView with layout parameters, a default style and a set 708 * of custom Javscript interfaces to be added to the WebView at initialization 709 * time. This guraratees that these interfaces will be available when the JS 710 * context is initialized. 711 * @param context A Context object used to access application assets. 712 * @param attrs An AttributeSet passed to our parent. 713 * @param defStyle The default style resource ID. 714 * @param javascriptInterfaces is a Map of intareface names, as keys, and 715 * object implementing those interfaces, as values. 716 * @hide pending API council approval. 717 */ 718 protected WebView(Context context, AttributeSet attrs, int defStyle, 719 Map<String, Object> javascriptInterfaces) { 720 super(context, attrs, defStyle); 721 init(); 722 723 mCallbackProxy = new CallbackProxy(context, this); 724 mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); 725 mDatabase = WebViewDatabase.getInstance(context); 726 mScroller = new Scroller(context); 727 728 mViewManager = new ViewManager(this); 729 730 mZoomButtonsController = new ZoomButtonsController(this); 731 mZoomButtonsController.setOnZoomListener(mZoomListener); 732 // ZoomButtonsController positions the buttons at the bottom, but in 733 // the middle. Change their layout parameters so they appear on the 734 // right. 735 View controls = mZoomButtonsController.getZoomControls(); 736 ViewGroup.LayoutParams params = controls.getLayoutParams(); 737 if (params instanceof FrameLayout.LayoutParams) { 738 FrameLayout.LayoutParams frameParams = (FrameLayout.LayoutParams) 739 params; 740 frameParams.gravity = Gravity.RIGHT; 741 } 742 } 743 744 private void updateZoomButtonsEnabled() { 745 boolean canZoomIn = mActualScale < mMaxZoomScale; 746 boolean canZoomOut = mActualScale > mMinZoomScale && !mInZoomOverview; 747 if (!canZoomIn && !canZoomOut) { 748 // Hide the zoom in and out buttons, as well as the fit to page 749 // button, if the page cannot zoom 750 mZoomButtonsController.getZoomControls().setVisibility(View.GONE); 751 } else { 752 // Bring back the hidden zoom controls. 753 mZoomButtonsController.getZoomControls() 754 .setVisibility(View.VISIBLE); 755 // Set each one individually, as a page may be able to zoom in 756 // or out. 757 mZoomButtonsController.setZoomInEnabled(canZoomIn); 758 mZoomButtonsController.setZoomOutEnabled(canZoomOut); 759 } 760 } 761 762 private void init() { 763 setWillNotDraw(false); 764 setFocusable(true); 765 setFocusableInTouchMode(true); 766 setClickable(true); 767 setLongClickable(true); 768 769 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 770 int slop = configuration.getScaledTouchSlop(); 771 mTouchSlopSquare = slop * slop; 772 mMinLockSnapReverseDistance = slop; 773 slop = configuration.getScaledDoubleTapSlop(); 774 mDoubleTapSlopSquare = slop * slop; 775 final float density = getContext().getResources().getDisplayMetrics().density; 776 // use one line height, 16 based on our current default font, for how 777 // far we allow a touch be away from the edge of a link 778 mNavSlop = (int) (16 * density); 779 // density adjusted scale factors 780 DEFAULT_SCALE_PERCENT = (int) (100 * density); 781 mDefaultScale = density; 782 mActualScale = density; 783 mInvActualScale = 1 / density; 784 DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; 785 DEFAULT_MIN_ZOOM_SCALE = 0.25f * density; 786 mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; 787 mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; 788 mMaximumFling = configuration.getScaledMaximumFlingVelocity(); 789 } 790 791 /* package */void updateDefaultZoomDensity(int zoomDensity) { 792 final float density = getContext().getResources().getDisplayMetrics().density 793 * 100 / zoomDensity; 794 if (Math.abs(density - mDefaultScale) > 0.01) { 795 float scaleFactor = density / mDefaultScale; 796 // adjust the limits 797 mNavSlop = (int) (16 * density); 798 DEFAULT_SCALE_PERCENT = (int) (100 * density); 799 DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; 800 DEFAULT_MIN_ZOOM_SCALE = 0.25f * density; 801 mDefaultScale = density; 802 mMaxZoomScale *= scaleFactor; 803 mMinZoomScale *= scaleFactor; 804 setNewZoomScale(mActualScale * scaleFactor, false); 805 } 806 } 807 808 /* package */ boolean onSavePassword(String schemePlusHost, String username, 809 String password, final Message resumeMsg) { 810 boolean rVal = false; 811 if (resumeMsg == null) { 812 // null resumeMsg implies saving password silently 813 mDatabase.setUsernamePassword(schemePlusHost, username, password); 814 } else { 815 final Message remember = mPrivateHandler.obtainMessage( 816 REMEMBER_PASSWORD); 817 remember.getData().putString("host", schemePlusHost); 818 remember.getData().putString("username", username); 819 remember.getData().putString("password", password); 820 remember.obj = resumeMsg; 821 822 final Message neverRemember = mPrivateHandler.obtainMessage( 823 NEVER_REMEMBER_PASSWORD); 824 neverRemember.getData().putString("host", schemePlusHost); 825 neverRemember.getData().putString("username", username); 826 neverRemember.getData().putString("password", password); 827 neverRemember.obj = resumeMsg; 828 829 new AlertDialog.Builder(getContext()) 830 .setTitle(com.android.internal.R.string.save_password_label) 831 .setMessage(com.android.internal.R.string.save_password_message) 832 .setPositiveButton(com.android.internal.R.string.save_password_notnow, 833 new DialogInterface.OnClickListener() { 834 public void onClick(DialogInterface dialog, int which) { 835 resumeMsg.sendToTarget(); 836 } 837 }) 838 .setNeutralButton(com.android.internal.R.string.save_password_remember, 839 new DialogInterface.OnClickListener() { 840 public void onClick(DialogInterface dialog, int which) { 841 remember.sendToTarget(); 842 } 843 }) 844 .setNegativeButton(com.android.internal.R.string.save_password_never, 845 new DialogInterface.OnClickListener() { 846 public void onClick(DialogInterface dialog, int which) { 847 neverRemember.sendToTarget(); 848 } 849 }) 850 .setOnCancelListener(new OnCancelListener() { 851 public void onCancel(DialogInterface dialog) { 852 resumeMsg.sendToTarget(); 853 } 854 }).show(); 855 // Return true so that WebViewCore will pause while the dialog is 856 // up. 857 rVal = true; 858 } 859 return rVal; 860 } 861 862 @Override 863 public void setScrollBarStyle(int style) { 864 if (style == View.SCROLLBARS_INSIDE_INSET 865 || style == View.SCROLLBARS_OUTSIDE_INSET) { 866 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false; 867 } else { 868 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true; 869 } 870 super.setScrollBarStyle(style); 871 } 872 873 /** 874 * Specify whether the horizontal scrollbar has overlay style. 875 * @param overlay TRUE if horizontal scrollbar should have overlay style. 876 */ 877 public void setHorizontalScrollbarOverlay(boolean overlay) { 878 mOverlayHorizontalScrollbar = overlay; 879 } 880 881 /** 882 * Specify whether the vertical scrollbar has overlay style. 883 * @param overlay TRUE if vertical scrollbar should have overlay style. 884 */ 885 public void setVerticalScrollbarOverlay(boolean overlay) { 886 mOverlayVerticalScrollbar = overlay; 887 } 888 889 /** 890 * Return whether horizontal scrollbar has overlay style 891 * @return TRUE if horizontal scrollbar has overlay style. 892 */ 893 public boolean overlayHorizontalScrollbar() { 894 return mOverlayHorizontalScrollbar; 895 } 896 897 /** 898 * Return whether vertical scrollbar has overlay style 899 * @return TRUE if vertical scrollbar has overlay style. 900 */ 901 public boolean overlayVerticalScrollbar() { 902 return mOverlayVerticalScrollbar; 903 } 904 905 /* 906 * Return the width of the view where the content of WebView should render 907 * to. 908 * Note: this can be called from WebCoreThread. 909 */ 910 /* package */ int getViewWidth() { 911 if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) { 912 return getWidth(); 913 } else { 914 return getWidth() - getVerticalScrollbarWidth(); 915 } 916 } 917 918 /* 919 * returns the height of the titlebarview (if any). Does not care about 920 * scrolling 921 */ 922 private int getTitleHeight() { 923 return mTitleBar != null ? mTitleBar.getHeight() : 0; 924 } 925 926 /* 927 * Return the amount of the titlebarview (if any) that is visible 928 */ 929 private int getVisibleTitleHeight() { 930 return Math.max(getTitleHeight() - mScrollY, 0); 931 } 932 933 /* 934 * Return the height of the view where the content of WebView should render 935 * to. Note that this excludes mTitleBar, if there is one. 936 * Note: this can be called from WebCoreThread. 937 */ 938 /* package */ int getViewHeight() { 939 int height = getHeight(); 940 if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) { 941 height -= getHorizontalScrollbarHeight(); 942 } 943 return height - getVisibleTitleHeight(); 944 } 945 946 /** 947 * @return The SSL certificate for the main top-level page or null if 948 * there is no certificate (the site is not secure). 949 */ 950 public SslCertificate getCertificate() { 951 return mCertificate; 952 } 953 954 /** 955 * Sets the SSL certificate for the main top-level page. 956 */ 957 public void setCertificate(SslCertificate certificate) { 958 // here, the certificate can be null (if the site is not secure) 959 mCertificate = certificate; 960 } 961 962 //------------------------------------------------------------------------- 963 // Methods called by activity 964 //------------------------------------------------------------------------- 965 966 /** 967 * Save the username and password for a particular host in the WebView's 968 * internal database. 969 * @param host The host that required the credentials. 970 * @param username The username for the given host. 971 * @param password The password for the given host. 972 */ 973 public void savePassword(String host, String username, String password) { 974 mDatabase.setUsernamePassword(host, username, password); 975 } 976 977 /** 978 * Set the HTTP authentication credentials for a given host and realm. 979 * 980 * @param host The host for the credentials. 981 * @param realm The realm for the credentials. 982 * @param username The username for the password. If it is null, it means 983 * password can't be saved. 984 * @param password The password 985 */ 986 public void setHttpAuthUsernamePassword(String host, String realm, 987 String username, String password) { 988 mDatabase.setHttpAuthUsernamePassword(host, realm, username, password); 989 } 990 991 /** 992 * Retrieve the HTTP authentication username and password for a given 993 * host & realm pair 994 * 995 * @param host The host for which the credentials apply. 996 * @param realm The realm for which the credentials apply. 997 * @return String[] if found, String[0] is username, which can be null and 998 * String[1] is password. Return null if it can't find anything. 999 */ 1000 public String[] getHttpAuthUsernamePassword(String host, String realm) { 1001 return mDatabase.getHttpAuthUsernamePassword(host, realm); 1002 } 1003 1004 /** 1005 * Destroy the internal state of the WebView. This method should be called 1006 * after the WebView has been removed from the view system. No other 1007 * methods may be called on a WebView after destroy. 1008 */ 1009 public void destroy() { 1010 clearTextEntry(); 1011 if (mWebViewCore != null) { 1012 // Set the handlers to null before destroying WebViewCore so no 1013 // more messages will be posted. 1014 mCallbackProxy.setWebViewClient(null); 1015 mCallbackProxy.setWebChromeClient(null); 1016 // Tell WebViewCore to destroy itself 1017 WebViewCore webViewCore = mWebViewCore; 1018 mWebViewCore = null; // prevent using partial webViewCore 1019 webViewCore.destroy(); 1020 // Remove any pending messages that might not be serviced yet. 1021 mPrivateHandler.removeCallbacksAndMessages(null); 1022 mCallbackProxy.removeCallbacksAndMessages(null); 1023 // Wake up the WebCore thread just in case it is waiting for a 1024 // javascript dialog. 1025 synchronized (mCallbackProxy) { 1026 mCallbackProxy.notify(); 1027 } 1028 } 1029 if (mNativeClass != 0) { 1030 nativeDestroy(); 1031 mNativeClass = 0; 1032 } 1033 } 1034 1035 /** 1036 * Enables platform notifications of data state and proxy changes. 1037 */ 1038 public static void enablePlatformNotifications() { 1039 Network.enablePlatformNotifications(); 1040 } 1041 1042 /** 1043 * If platform notifications are enabled, this should be called 1044 * from the Activity's onPause() or onStop(). 1045 */ 1046 public static void disablePlatformNotifications() { 1047 Network.disablePlatformNotifications(); 1048 } 1049 1050 /** 1051 * Sets JavaScript engine flags. 1052 * 1053 * @param flags JS engine flags in a String 1054 * 1055 * @hide pending API solidification 1056 */ 1057 public void setJsFlags(String flags) { 1058 mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags); 1059 } 1060 1061 /** 1062 * Inform WebView of the network state. This is used to set 1063 * the javascript property window.navigator.isOnline and 1064 * generates the online/offline event as specified in HTML5, sec. 5.7.7 1065 * @param networkUp boolean indicating if network is available 1066 */ 1067 public void setNetworkAvailable(boolean networkUp) { 1068 mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE, 1069 networkUp ? 1 : 0, 0); 1070 } 1071 1072 /** 1073 * Save the state of this WebView used in 1074 * {@link android.app.Activity#onSaveInstanceState}. Please note that this 1075 * method no longer stores the display data for this WebView. The previous 1076 * behavior could potentially leak files if {@link #restoreState} was never 1077 * called. See {@link #savePicture} and {@link #restorePicture} for saving 1078 * and restoring the display data. 1079 * @param outState The Bundle to store the WebView state. 1080 * @return The same copy of the back/forward list used to save the state. If 1081 * saveState fails, the returned list will be null. 1082 * @see #savePicture 1083 * @see #restorePicture 1084 */ 1085 public WebBackForwardList saveState(Bundle outState) { 1086 if (outState == null) { 1087 return null; 1088 } 1089 // We grab a copy of the back/forward list because a client of WebView 1090 // may have invalidated the history list by calling clearHistory. 1091 WebBackForwardList list = copyBackForwardList(); 1092 final int currentIndex = list.getCurrentIndex(); 1093 final int size = list.getSize(); 1094 // We should fail saving the state if the list is empty or the index is 1095 // not in a valid range. 1096 if (currentIndex < 0 || currentIndex >= size || size == 0) { 1097 return null; 1098 } 1099 outState.putInt("index", currentIndex); 1100 // FIXME: This should just be a byte[][] instead of ArrayList but 1101 // Parcel.java does not have the code to handle multi-dimensional 1102 // arrays. 1103 ArrayList<byte[]> history = new ArrayList<byte[]>(size); 1104 for (int i = 0; i < size; i++) { 1105 WebHistoryItem item = list.getItemAtIndex(i); 1106 byte[] data = item.getFlattenedData(); 1107 if (data == null) { 1108 // It would be very odd to not have any data for a given history 1109 // item. And we will fail to rebuild the history list without 1110 // flattened data. 1111 return null; 1112 } 1113 history.add(data); 1114 } 1115 outState.putSerializable("history", history); 1116 if (mCertificate != null) { 1117 outState.putBundle("certificate", 1118 SslCertificate.saveState(mCertificate)); 1119 } 1120 return list; 1121 } 1122 1123 /** 1124 * Save the current display data to the Bundle given. Used in conjunction 1125 * with {@link #saveState}. 1126 * @param b A Bundle to store the display data. 1127 * @param dest The file to store the serialized picture data. Will be 1128 * overwritten with this WebView's picture data. 1129 * @return True if the picture was successfully saved. 1130 */ 1131 public boolean savePicture(Bundle b, File dest) { 1132 if (dest == null || b == null) { 1133 return false; 1134 } 1135 final Picture p = capturePicture(); 1136 try { 1137 final FileOutputStream out = new FileOutputStream(dest); 1138 p.writeToStream(out); 1139 out.close(); 1140 } catch (FileNotFoundException e){ 1141 e.printStackTrace(); 1142 } catch (IOException e) { 1143 e.printStackTrace(); 1144 } catch (RuntimeException e) { 1145 e.printStackTrace(); 1146 } 1147 if (dest.length() > 0) { 1148 b.putInt("scrollX", mScrollX); 1149 b.putInt("scrollY", mScrollY); 1150 b.putFloat("scale", mActualScale); 1151 if (mInZoomOverview) { 1152 b.putFloat("lastScale", mLastScale); 1153 } 1154 return true; 1155 } 1156 return false; 1157 } 1158 1159 /** 1160 * Restore the display data that was save in {@link #savePicture}. Used in 1161 * conjunction with {@link #restoreState}. 1162 * @param b A Bundle containing the saved display data. 1163 * @param src The file where the picture data was stored. 1164 * @return True if the picture was successfully restored. 1165 */ 1166 public boolean restorePicture(Bundle b, File src) { 1167 if (src == null || b == null) { 1168 return false; 1169 } 1170 if (src.exists()) { 1171 Picture p = null; 1172 try { 1173 final FileInputStream in = new FileInputStream(src); 1174 p = Picture.createFromStream(in); 1175 in.close(); 1176 } catch (FileNotFoundException e){ 1177 e.printStackTrace(); 1178 } catch (RuntimeException e) { 1179 e.printStackTrace(); 1180 } catch (IOException e) { 1181 e.printStackTrace(); 1182 } 1183 if (p != null) { 1184 int sx = b.getInt("scrollX", 0); 1185 int sy = b.getInt("scrollY", 0); 1186 float scale = b.getFloat("scale", 1.0f); 1187 mDrawHistory = true; 1188 mHistoryPicture = p; 1189 mScrollX = sx; 1190 mScrollY = sy; 1191 mHistoryWidth = Math.round(p.getWidth() * scale); 1192 mHistoryHeight = Math.round(p.getHeight() * scale); 1193 // as getWidth() / getHeight() of the view are not 1194 // available yet, set up mActualScale, so that when 1195 // onSizeChanged() is called, the rest will be set 1196 // correctly 1197 mActualScale = scale; 1198 float lastScale = b.getFloat("lastScale", -1.0f); 1199 if (lastScale > 0) { 1200 mInZoomOverview = true; 1201 mLastScale = lastScale; 1202 } else { 1203 mInZoomOverview = false; 1204 } 1205 invalidate(); 1206 return true; 1207 } 1208 } 1209 return false; 1210 } 1211 1212 /** 1213 * Restore the state of this WebView from the given map used in 1214 * {@link android.app.Activity#onRestoreInstanceState}. This method should 1215 * be called to restore the state of the WebView before using the object. If 1216 * it is called after the WebView has had a chance to build state (load 1217 * pages, create a back/forward list, etc.) there may be undesirable 1218 * side-effects. Please note that this method no longer restores the 1219 * display data for this WebView. See {@link #savePicture} and {@link 1220 * #restorePicture} for saving and restoring the display data. 1221 * @param inState The incoming Bundle of state. 1222 * @return The restored back/forward list or null if restoreState failed. 1223 * @see #savePicture 1224 * @see #restorePicture 1225 */ 1226 public WebBackForwardList restoreState(Bundle inState) { 1227 WebBackForwardList returnList = null; 1228 if (inState == null) { 1229 return returnList; 1230 } 1231 if (inState.containsKey("index") && inState.containsKey("history")) { 1232 mCertificate = SslCertificate.restoreState( 1233 inState.getBundle("certificate")); 1234 1235 final WebBackForwardList list = mCallbackProxy.getBackForwardList(); 1236 final int index = inState.getInt("index"); 1237 // We can't use a clone of the list because we need to modify the 1238 // shared copy, so synchronize instead to prevent concurrent 1239 // modifications. 1240 synchronized (list) { 1241 final List<byte[]> history = 1242 (List<byte[]>) inState.getSerializable("history"); 1243 final int size = history.size(); 1244 // Check the index bounds so we don't crash in native code while 1245 // restoring the history index. 1246 if (index < 0 || index >= size) { 1247 return null; 1248 } 1249 for (int i = 0; i < size; i++) { 1250 byte[] data = history.remove(0); 1251 if (data == null) { 1252 // If we somehow have null data, we cannot reconstruct 1253 // the item and thus our history list cannot be rebuilt. 1254 return null; 1255 } 1256 WebHistoryItem item = new WebHistoryItem(data); 1257 list.addHistoryItem(item); 1258 } 1259 // Grab the most recent copy to return to the caller. 1260 returnList = copyBackForwardList(); 1261 // Update the copy to have the correct index. 1262 returnList.setCurrentIndex(index); 1263 } 1264 // Remove all pending messages because we are restoring previous 1265 // state. 1266 mWebViewCore.removeMessages(); 1267 // Send a restore state message. 1268 mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); 1269 } 1270 return returnList; 1271 } 1272 1273 /** 1274 * Load the given url. 1275 * @param url The url of the resource to load. 1276 */ 1277 public void loadUrl(String url) { 1278 if (url == null) { 1279 return; 1280 } 1281 switchOutDrawHistory(); 1282 mWebViewCore.sendMessage(EventHub.LOAD_URL, url); 1283 clearTextEntry(); 1284 } 1285 1286 /** 1287 * Load the url with postData using "POST" method into the WebView. If url 1288 * is not a network url, it will be loaded with {link 1289 * {@link #loadUrl(String)} instead. 1290 * 1291 * @param url The url of the resource to load. 1292 * @param postData The data will be passed to "POST" request. 1293 */ 1294 public void postUrl(String url, byte[] postData) { 1295 if (URLUtil.isNetworkUrl(url)) { 1296 switchOutDrawHistory(); 1297 WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData(); 1298 arg.mUrl = url; 1299 arg.mPostData = postData; 1300 mWebViewCore.sendMessage(EventHub.POST_URL, arg); 1301 clearTextEntry(); 1302 } else { 1303 loadUrl(url); 1304 } 1305 } 1306 1307 /** 1308 * Load the given data into the WebView. This will load the data into 1309 * WebView using the data: scheme. Content loaded through this mechanism 1310 * does not have the ability to load content from the network. 1311 * @param data A String of data in the given encoding. 1312 * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg 1313 * @param encoding The encoding of the data. i.e. utf-8, base64 1314 */ 1315 public void loadData(String data, String mimeType, String encoding) { 1316 loadUrl("data:" + mimeType + ";" + encoding + "," + data); 1317 } 1318 1319 /** 1320 * Load the given data into the WebView, use the provided URL as the base 1321 * URL for the content. The base URL is the URL that represents the page 1322 * that is loaded through this interface. As such, it is used for the 1323 * history entry and to resolve any relative URLs. The failUrl is used if 1324 * browser fails to load the data provided. If it is empty or null, and the 1325 * load fails, then no history entry is created. 1326 * <p> 1327 * Note for post 1.0. Due to the change in the WebKit, the access to asset 1328 * files through "file:///android_asset/" for the sub resources is more 1329 * restricted. If you provide null or empty string as baseUrl, you won't be 1330 * able to access asset files. If the baseUrl is anything other than 1331 * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for 1332 * sub resources. 1333 * 1334 * @param baseUrl Url to resolve relative paths with, if null defaults to 1335 * "about:blank" 1336 * @param data A String of data in the given encoding. 1337 * @param mimeType The MIMEType of the data. i.e. text/html. If null, 1338 * defaults to "text/html" 1339 * @param encoding The encoding of the data. i.e. utf-8, us-ascii 1340 * @param failUrl URL to use if the content fails to load or null. 1341 */ 1342 public void loadDataWithBaseURL(String baseUrl, String data, 1343 String mimeType, String encoding, String failUrl) { 1344 1345 if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { 1346 loadData(data, mimeType, encoding); 1347 return; 1348 } 1349 switchOutDrawHistory(); 1350 WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData(); 1351 arg.mBaseUrl = baseUrl; 1352 arg.mData = data; 1353 arg.mMimeType = mimeType; 1354 arg.mEncoding = encoding; 1355 arg.mFailUrl = failUrl; 1356 mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg); 1357 clearTextEntry(); 1358 } 1359 1360 /** 1361 * Stop the current load. 1362 */ 1363 public void stopLoading() { 1364 // TODO: should we clear all the messages in the queue before sending 1365 // STOP_LOADING? 1366 switchOutDrawHistory(); 1367 mWebViewCore.sendMessage(EventHub.STOP_LOADING); 1368 } 1369 1370 /** 1371 * Reload the current url. 1372 */ 1373 public void reload() { 1374 clearTextEntry(); 1375 switchOutDrawHistory(); 1376 mWebViewCore.sendMessage(EventHub.RELOAD); 1377 } 1378 1379 /** 1380 * Return true if this WebView has a back history item. 1381 * @return True iff this WebView has a back history item. 1382 */ 1383 public boolean canGoBack() { 1384 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1385 synchronized (l) { 1386 if (l.getClearPending()) { 1387 return false; 1388 } else { 1389 return l.getCurrentIndex() > 0; 1390 } 1391 } 1392 } 1393 1394 /** 1395 * Go back in the history of this WebView. 1396 */ 1397 public void goBack() { 1398 goBackOrForward(-1); 1399 } 1400 1401 /** 1402 * Return true if this WebView has a forward history item. 1403 * @return True iff this Webview has a forward history item. 1404 */ 1405 public boolean canGoForward() { 1406 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1407 synchronized (l) { 1408 if (l.getClearPending()) { 1409 return false; 1410 } else { 1411 return l.getCurrentIndex() < l.getSize() - 1; 1412 } 1413 } 1414 } 1415 1416 /** 1417 * Go forward in the history of this WebView. 1418 */ 1419 public void goForward() { 1420 goBackOrForward(1); 1421 } 1422 1423 /** 1424 * Return true if the page can go back or forward the given 1425 * number of steps. 1426 * @param steps The negative or positive number of steps to move the 1427 * history. 1428 */ 1429 public boolean canGoBackOrForward(int steps) { 1430 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1431 synchronized (l) { 1432 if (l.getClearPending()) { 1433 return false; 1434 } else { 1435 int newIndex = l.getCurrentIndex() + steps; 1436 return newIndex >= 0 && newIndex < l.getSize(); 1437 } 1438 } 1439 } 1440 1441 /** 1442 * Go to the history item that is the number of steps away from 1443 * the current item. Steps is negative if backward and positive 1444 * if forward. 1445 * @param steps The number of steps to take back or forward in the back 1446 * forward list. 1447 */ 1448 public void goBackOrForward(int steps) { 1449 goBackOrForward(steps, false); 1450 } 1451 1452 private void goBackOrForward(int steps, boolean ignoreSnapshot) { 1453 // every time we go back or forward, we want to reset the 1454 // WebView certificate: 1455 // if the new site is secure, we will reload it and get a 1456 // new certificate set; 1457 // if the new site is not secure, the certificate must be 1458 // null, and that will be the case 1459 mCertificate = null; 1460 if (steps != 0) { 1461 clearTextEntry(); 1462 mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps, 1463 ignoreSnapshot ? 1 : 0); 1464 } 1465 } 1466 1467 private boolean extendScroll(int y) { 1468 int finalY = mScroller.getFinalY(); 1469 int newY = pinLocY(finalY + y); 1470 if (newY == finalY) return false; 1471 mScroller.setFinalY(newY); 1472 mScroller.extendDuration(computeDuration(0, y)); 1473 return true; 1474 } 1475 1476 /** 1477 * Scroll the contents of the view up by half the view size 1478 * @param top true to jump to the top of the page 1479 * @return true if the page was scrolled 1480 */ 1481 public boolean pageUp(boolean top) { 1482 if (mNativeClass == 0) { 1483 return false; 1484 } 1485 nativeClearCursor(); // start next trackball movement from page edge 1486 if (top) { 1487 // go to the top of the document 1488 return pinScrollTo(mScrollX, 0, true, 0); 1489 } 1490 // Page up 1491 int h = getHeight(); 1492 int y; 1493 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1494 y = -h + PAGE_SCROLL_OVERLAP; 1495 } else { 1496 y = -h / 2; 1497 } 1498 mUserScroll = true; 1499 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 1500 : extendScroll(y); 1501 } 1502 1503 /** 1504 * Scroll the contents of the view down by half the page size 1505 * @param bottom true to jump to bottom of page 1506 * @return true if the page was scrolled 1507 */ 1508 public boolean pageDown(boolean bottom) { 1509 if (mNativeClass == 0) { 1510 return false; 1511 } 1512 nativeClearCursor(); // start next trackball movement from page edge 1513 if (bottom) { 1514 return pinScrollTo(mScrollX, mContentHeight, true, 0); 1515 } 1516 // Page down. 1517 int h = getHeight(); 1518 int y; 1519 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1520 y = h - PAGE_SCROLL_OVERLAP; 1521 } else { 1522 y = h / 2; 1523 } 1524 mUserScroll = true; 1525 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 1526 : extendScroll(y); 1527 } 1528 1529 /** 1530 * Clear the view so that onDraw() will draw nothing but white background, 1531 * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY 1532 */ 1533 public void clearView() { 1534 mContentWidth = 0; 1535 mContentHeight = 0; 1536 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); 1537 } 1538 1539 /** 1540 * Return a new picture that captures the current display of the webview. 1541 * This is a copy of the display, and will be unaffected if the webview 1542 * later loads a different URL. 1543 * 1544 * @return a picture containing the current contents of the view. Note this 1545 * picture is of the entire document, and is not restricted to the 1546 * bounds of the view. 1547 */ 1548 public Picture capturePicture() { 1549 if (null == mWebViewCore) return null; // check for out of memory tab 1550 return mWebViewCore.copyContentPicture(); 1551 } 1552 1553 /** 1554 * Return true if the browser is displaying a TextView for text input. 1555 */ 1556 private boolean inEditingMode() { 1557 return mWebTextView != null && mWebTextView.getParent() != null 1558 && mWebTextView.hasFocus(); 1559 } 1560 1561 private void clearTextEntry() { 1562 if (inEditingMode()) { 1563 mWebTextView.remove(); 1564 } 1565 } 1566 1567 /** 1568 * Return the current scale of the WebView 1569 * @return The current scale. 1570 */ 1571 public float getScale() { 1572 return mActualScale; 1573 } 1574 1575 /** 1576 * Set the initial scale for the WebView. 0 means default. If 1577 * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the 1578 * way. Otherwise it starts with 100%. If initial scale is greater than 0, 1579 * WebView starts will this value as initial scale. 1580 * 1581 * @param scaleInPercent The initial scale in percent. 1582 */ 1583 public void setInitialScale(int scaleInPercent) { 1584 mInitialScale = scaleInPercent; 1585 } 1586 1587 /** 1588 * Invoke the graphical zoom picker widget for this WebView. This will 1589 * result in the zoom widget appearing on the screen to control the zoom 1590 * level of this WebView. 1591 */ 1592 public void invokeZoomPicker() { 1593 if (!getSettings().supportZoom()) { 1594 Log.w(LOGTAG, "This WebView doesn't support zoom."); 1595 return; 1596 } 1597 clearTextEntry(); 1598 if (getSettings().getBuiltInZoomControls()) { 1599 mZoomButtonsController.setVisible(true); 1600 } else { 1601 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 1602 mPrivateHandler.postDelayed(mZoomControlRunnable, 1603 ZOOM_CONTROLS_TIMEOUT); 1604 } 1605 } 1606 1607 /** 1608 * Return a HitTestResult based on the current cursor node. If a HTML::a tag 1609 * is found and the anchor has a non-javascript url, the HitTestResult type 1610 * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the 1611 * anchor does not have a url or if it is a javascript url, the type will 1612 * be UNKNOWN_TYPE and the url has to be retrieved through 1613 * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is 1614 * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in 1615 * the "extra" field. A type of 1616 * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as 1617 * a child node. If a phone number is found, the HitTestResult type is set 1618 * to PHONE_TYPE and the phone number is set in the "extra" field of 1619 * HitTestResult. If a map address is found, the HitTestResult type is set 1620 * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. 1621 * If an email address is found, the HitTestResult type is set to EMAIL_TYPE 1622 * and the email is set in the "extra" field of HitTestResult. Otherwise, 1623 * HitTestResult type is set to UNKNOWN_TYPE. 1624 */ 1625 public HitTestResult getHitTestResult() { 1626 if (mNativeClass == 0) { 1627 return null; 1628 } 1629 1630 HitTestResult result = new HitTestResult(); 1631 if (nativeHasCursorNode()) { 1632 if (nativeCursorIsTextInput()) { 1633 result.setType(HitTestResult.EDIT_TEXT_TYPE); 1634 } else { 1635 String text = nativeCursorText(); 1636 if (text != null) { 1637 if (text.startsWith(SCHEME_TEL)) { 1638 result.setType(HitTestResult.PHONE_TYPE); 1639 result.setExtra(text.substring(SCHEME_TEL.length())); 1640 } else if (text.startsWith(SCHEME_MAILTO)) { 1641 result.setType(HitTestResult.EMAIL_TYPE); 1642 result.setExtra(text.substring(SCHEME_MAILTO.length())); 1643 } else if (text.startsWith(SCHEME_GEO)) { 1644 result.setType(HitTestResult.GEO_TYPE); 1645 result.setExtra(URLDecoder.decode(text 1646 .substring(SCHEME_GEO.length()))); 1647 } else if (nativeCursorIsAnchor()) { 1648 result.setType(HitTestResult.SRC_ANCHOR_TYPE); 1649 result.setExtra(text); 1650 } 1651 } 1652 } 1653 } 1654 int type = result.getType(); 1655 if (type == HitTestResult.UNKNOWN_TYPE 1656 || type == HitTestResult.SRC_ANCHOR_TYPE) { 1657 // Now check to see if it is an image. 1658 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 1659 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 1660 String text = nativeImageURI(contentX, contentY); 1661 if (text != null) { 1662 result.setType(type == HitTestResult.UNKNOWN_TYPE ? 1663 HitTestResult.IMAGE_TYPE : 1664 HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 1665 result.setExtra(text); 1666 } 1667 } 1668 return result; 1669 } 1670 1671 /** 1672 * Request the href of an anchor element due to getFocusNodePath returning 1673 * "href." If hrefMsg is null, this method returns immediately and does not 1674 * dispatch hrefMsg to its target. 1675 * 1676 * @param hrefMsg This message will be dispatched with the result of the 1677 * request as the data member with "url" as key. The result can 1678 * be null. 1679 */ 1680 // FIXME: API change required to change the name of this function. We now 1681 // look at the cursor node, and not the focus node. Also, what is 1682 // getFocusNodePath? 1683 public void requestFocusNodeHref(Message hrefMsg) { 1684 if (hrefMsg == null || mNativeClass == 0) { 1685 return; 1686 } 1687 if (nativeCursorIsAnchor()) { 1688 mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF, 1689 nativeCursorFramePointer(), nativeCursorNodePointer(), 1690 hrefMsg); 1691 } 1692 } 1693 1694 /** 1695 * Request the url of the image last touched by the user. msg will be sent 1696 * to its target with a String representing the url as its object. 1697 * 1698 * @param msg This message will be dispatched with the result of the request 1699 * as the data member with "url" as key. The result can be null. 1700 */ 1701 public void requestImageRef(Message msg) { 1702 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 1703 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 1704 String ref = nativeImageURI(contentX, contentY); 1705 Bundle data = msg.getData(); 1706 data.putString("url", ref); 1707 msg.setData(data); 1708 msg.sendToTarget(); 1709 } 1710 1711 private static int pinLoc(int x, int viewMax, int docMax) { 1712// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax); 1713 if (docMax < viewMax) { // the doc has room on the sides for "blank" 1714 // pin the short document to the top/left of the screen 1715 x = 0; 1716// Log.d(LOGTAG, "--- center " + x); 1717 } else if (x < 0) { 1718 x = 0; 1719// Log.d(LOGTAG, "--- zero"); 1720 } else if (x + viewMax > docMax) { 1721 x = docMax - viewMax; 1722// Log.d(LOGTAG, "--- pin " + x); 1723 } 1724 return x; 1725 } 1726 1727 // Expects x in view coordinates 1728 private int pinLocX(int x) { 1729 return pinLoc(x, getViewWidth(), computeHorizontalScrollRange()); 1730 } 1731 1732 // Expects y in view coordinates 1733 private int pinLocY(int y) { 1734 int titleH = getTitleHeight(); 1735 // if the titlebar is still visible, just pin against 0 1736 if (y <= titleH) { 1737 return Math.max(y, 0); 1738 } 1739 // convert to 0-based coordinate (subtract the title height) 1740 // pin(), and then add the title height back in 1741 return pinLoc(y - titleH, getViewHeight(), 1742 computeVerticalScrollRange()) + titleH; 1743 } 1744 1745 /** 1746 * A title bar which is embedded in this WebView, and scrolls along with it 1747 * vertically, but not horizontally. 1748 */ 1749 private View mTitleBar; 1750 1751 /** 1752 * Since we draw the title bar ourselves, we removed the shadow from the 1753 * browser's activity. We do want a shadow at the bottom of the title bar, 1754 * or at the top of the screen if the title bar is not visible. This 1755 * drawable serves that purpose. 1756 */ 1757 private Drawable mTitleShadow; 1758 1759 /** 1760 * Add or remove a title bar to be embedded into the WebView, and scroll 1761 * along with it vertically, while remaining in view horizontally. Pass 1762 * null to remove the title bar from the WebView, and return to drawing 1763 * the WebView normally without translating to account for the title bar. 1764 * @hide 1765 */ 1766 public void setEmbeddedTitleBar(View v) { 1767 if (mTitleBar == v) return; 1768 if (mTitleBar != null) { 1769 removeView(mTitleBar); 1770 } 1771 if (null != v) { 1772 addView(v, new AbsoluteLayout.LayoutParams( 1773 ViewGroup.LayoutParams.FILL_PARENT, 1774 ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0)); 1775 if (mTitleShadow == null) { 1776 mTitleShadow = (Drawable) mContext.getResources().getDrawable( 1777 com.android.internal.R.drawable.title_bar_shadow); 1778 } 1779 } 1780 mTitleBar = v; 1781 } 1782 1783 /** 1784 * Given an x coordinate in view space, convert it to content space. Also 1785 * may be used for absolute heights (such as for the WebTextView's 1786 * textSize, which is unaffected by the height of the title bar). 1787 */ 1788 /*package*/ int viewToContentX(int x) { 1789 return Math.round(x * mInvActualScale); 1790 } 1791 1792 /** 1793 * Given a y coordinate in view space, convert it to content space. 1794 * Takes into account the height of the title bar if there is one 1795 * embedded into the WebView. 1796 */ 1797 /*package*/ int viewToContentY(int y) { 1798 return viewToContentX(y - getTitleHeight()); 1799 } 1800 1801 /** 1802 * Given a distance in content space, convert it to view space. Note: this 1803 * does not reflect translation, just scaling, so this should not be called 1804 * with coordinates, but should be called for dimensions like width or 1805 * height. 1806 */ 1807 /*package*/ int contentToViewDimension(int d) { 1808 return Math.round(d * mActualScale); 1809 } 1810 1811 /** 1812 * Given an x coordinate in content space, convert it to view 1813 * space. Also used for absolute heights. 1814 */ 1815 /*package*/ int contentToViewX(int x) { 1816 return contentToViewDimension(x); 1817 } 1818 1819 /** 1820 * Given a y coordinate in content space, convert it to view 1821 * space. Takes into account the height of the title bar. 1822 */ 1823 /*package*/ int contentToViewY(int y) { 1824 return contentToViewDimension(y) + getTitleHeight(); 1825 } 1826 1827 private Rect contentToViewRect(Rect x) { 1828 return new Rect(contentToViewX(x.left), contentToViewY(x.top), 1829 contentToViewX(x.right), contentToViewY(x.bottom)); 1830 } 1831 1832 /* To invalidate a rectangle in content coordinates, we need to transform 1833 the rect into view coordinates, so we can then call invalidate(...). 1834 1835 Normally, we would just call contentToView[XY](...), which eventually 1836 calls Math.round(coordinate * mActualScale). However, for invalidates, 1837 we need to account for the slop that occurs with antialiasing. To 1838 address that, we are a little more liberal in the size of the rect that 1839 we invalidate. 1840 1841 This liberal calculation calls floor() for the top/left, and ceil() for 1842 the bottom/right coordinates. This catches the possible extra pixels of 1843 antialiasing that we might have missed with just round(). 1844 */ 1845 1846 // Called by JNI to invalidate the View, given rectangle coordinates in 1847 // content space 1848 private void viewInvalidate(int l, int t, int r, int b) { 1849 final float scale = mActualScale; 1850 final int dy = getTitleHeight(); 1851 invalidate((int)Math.floor(l * scale), 1852 (int)Math.floor(t * scale) + dy, 1853 (int)Math.ceil(r * scale), 1854 (int)Math.ceil(b * scale) + dy); 1855 } 1856 1857 // Called by JNI to invalidate the View after a delay, given rectangle 1858 // coordinates in content space 1859 private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) { 1860 final float scale = mActualScale; 1861 final int dy = getTitleHeight(); 1862 postInvalidateDelayed(delay, 1863 (int)Math.floor(l * scale), 1864 (int)Math.floor(t * scale) + dy, 1865 (int)Math.ceil(r * scale), 1866 (int)Math.ceil(b * scale) + dy); 1867 } 1868 1869 private void invalidateContentRect(Rect r) { 1870 viewInvalidate(r.left, r.top, r.right, r.bottom); 1871 } 1872 1873 // stop the scroll animation, and don't let a subsequent fling add 1874 // to the existing velocity 1875 private void abortAnimation() { 1876 mScroller.abortAnimation(); 1877 mLastVelocity = 0; 1878 } 1879 1880 /* call from webcoreview.draw(), so we're still executing in the UI thread 1881 */ 1882 private void recordNewContentSize(int w, int h, boolean updateLayout) { 1883 1884 // premature data from webkit, ignore 1885 if ((w | h) == 0) { 1886 return; 1887 } 1888 1889 // don't abort a scroll animation if we didn't change anything 1890 if (mContentWidth != w || mContentHeight != h) { 1891 // record new dimensions 1892 mContentWidth = w; 1893 mContentHeight = h; 1894 // If history Picture is drawn, don't update scroll. They will be 1895 // updated when we get out of that mode. 1896 if (!mDrawHistory) { 1897 // repin our scroll, taking into account the new content size 1898 int oldX = mScrollX; 1899 int oldY = mScrollY; 1900 mScrollX = pinLocX(mScrollX); 1901 mScrollY = pinLocY(mScrollY); 1902 // android.util.Log.d("skia", "recordNewContentSize - 1903 // abortAnimation"); 1904 abortAnimation(); // just in case 1905 if (oldX != mScrollX || oldY != mScrollY) { 1906 sendOurVisibleRect(); 1907 } 1908 } 1909 } 1910 contentSizeChanged(updateLayout); 1911 } 1912 1913 private void setNewZoomScale(float scale, boolean force) { 1914 if (scale < mMinZoomScale) { 1915 scale = mMinZoomScale; 1916 } else if (scale > mMaxZoomScale) { 1917 scale = mMaxZoomScale; 1918 } 1919 if (scale != mActualScale || force) { 1920 if (mDrawHistory) { 1921 // If history Picture is drawn, don't update scroll. They will 1922 // be updated when we get out of that mode. 1923 if (scale != mActualScale && !mPreviewZoomOnly) { 1924 mCallbackProxy.onScaleChanged(mActualScale, scale); 1925 } 1926 mActualScale = scale; 1927 mInvActualScale = 1 / scale; 1928 if (!mPreviewZoomOnly) { 1929 sendViewSizeZoom(); 1930 } 1931 } else { 1932 // update our scroll so we don't appear to jump 1933 // i.e. keep the center of the doc in the center of the view 1934 1935 int oldX = mScrollX; 1936 int oldY = mScrollY; 1937 float ratio = scale * mInvActualScale; // old inverse 1938 float sx = ratio * oldX + (ratio - 1) * mZoomCenterX; 1939 float sy = ratio * oldY + (ratio - 1) 1940 * (mZoomCenterY - getTitleHeight()); 1941 1942 // now update our new scale and inverse 1943 if (scale != mActualScale && !mPreviewZoomOnly) { 1944 mCallbackProxy.onScaleChanged(mActualScale, scale); 1945 } 1946 mActualScale = scale; 1947 mInvActualScale = 1 / scale; 1948 1949 // Scale all the child views 1950 mViewManager.scaleAll(); 1951 1952 // as we don't have animation for scaling, don't do animation 1953 // for scrolling, as it causes weird intermediate state 1954 // pinScrollTo(Math.round(sx), Math.round(sy)); 1955 mScrollX = pinLocX(Math.round(sx)); 1956 mScrollY = pinLocY(Math.round(sy)); 1957 1958 if (!mPreviewZoomOnly) { 1959 sendViewSizeZoom(); 1960 sendOurVisibleRect(); 1961 } 1962 } 1963 } 1964 } 1965 1966 // Used to avoid sending many visible rect messages. 1967 private Rect mLastVisibleRectSent; 1968 private Rect mLastGlobalRect; 1969 1970 private Rect sendOurVisibleRect() { 1971 Rect rect = new Rect(); 1972 calcOurContentVisibleRect(rect); 1973 // Rect.equals() checks for null input. 1974 if (!rect.equals(mLastVisibleRectSent)) { 1975 Point pos = new Point(rect.left, rect.top); 1976 mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET, 1977 nativeMoveGeneration(), 0, pos); 1978 mLastVisibleRectSent = rect; 1979 } 1980 Rect globalRect = new Rect(); 1981 if (getGlobalVisibleRect(globalRect) 1982 && !globalRect.equals(mLastGlobalRect)) { 1983 if (DebugFlags.WEB_VIEW) { 1984 Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + "," 1985 + globalRect.top + ",r=" + globalRect.right + ",b=" 1986 + globalRect.bottom); 1987 } 1988 // TODO: the global offset is only used by windowRect() 1989 // in ChromeClientAndroid ; other clients such as touch 1990 // and mouse events could return view + screen relative points. 1991 mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect); 1992 mLastGlobalRect = globalRect; 1993 } 1994 return rect; 1995 } 1996 1997 // Sets r to be the visible rectangle of our webview in view coordinates 1998 private void calcOurVisibleRect(Rect r) { 1999 Point p = new Point(); 2000 getGlobalVisibleRect(r, p); 2001 r.offset(-p.x, -p.y); 2002 if (mFindIsUp) { 2003 r.bottom -= FIND_HEIGHT; 2004 } 2005 } 2006 2007 // Sets r to be our visible rectangle in content coordinates 2008 private void calcOurContentVisibleRect(Rect r) { 2009 calcOurVisibleRect(r); 2010 r.left = viewToContentX(r.left); 2011 r.top = viewToContentY(r.top); 2012 r.right = viewToContentX(r.right); 2013 r.bottom = viewToContentY(r.bottom); 2014 } 2015 2016 static class ViewSizeData { 2017 int mWidth; 2018 int mHeight; 2019 int mTextWrapWidth; 2020 float mScale; 2021 boolean mIgnoreHeight; 2022 } 2023 2024 /** 2025 * Compute unzoomed width and height, and if they differ from the last 2026 * values we sent, send them to webkit (to be used has new viewport) 2027 * 2028 * @return true if new values were sent 2029 */ 2030 private boolean sendViewSizeZoom() { 2031 int viewWidth = getViewWidth(); 2032 int newWidth = Math.round(viewWidth * mInvActualScale); 2033 int newHeight = Math.round(getViewHeight() * mInvActualScale); 2034 /* 2035 * Because the native side may have already done a layout before the 2036 * View system was able to measure us, we have to send a height of 0 to 2037 * remove excess whitespace when we grow our width. This will trigger a 2038 * layout and a change in content size. This content size change will 2039 * mean that contentSizeChanged will either call this method directly or 2040 * indirectly from onSizeChanged. 2041 */ 2042 if (newWidth > mLastWidthSent && mWrapContent) { 2043 newHeight = 0; 2044 } 2045 // Avoid sending another message if the dimensions have not changed. 2046 if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { 2047 ViewSizeData data = new ViewSizeData(); 2048 data.mWidth = newWidth; 2049 data.mHeight = newHeight; 2050 // while in zoom overview mode, the text are wrapped to the screen 2051 // width matching mLastScale. So that we don't trigger re-flow while 2052 // toggling between overview mode and normal mode. 2053 data.mTextWrapWidth = mInZoomOverview ? Math.round(viewWidth 2054 / mLastScale) : newWidth; 2055 data.mScale = mActualScale; 2056 data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure; 2057 mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data); 2058 mLastWidthSent = newWidth; 2059 mLastHeightSent = newHeight; 2060 return true; 2061 } 2062 return false; 2063 } 2064 2065 @Override 2066 protected int computeHorizontalScrollRange() { 2067 if (mDrawHistory) { 2068 return mHistoryWidth; 2069 } else { 2070 // to avoid rounding error caused unnecessary scrollbar, use floor 2071 return (int) Math.floor(mContentWidth * mActualScale); 2072 } 2073 } 2074 2075 // Make sure this stays in sync with the actual height of the FindDialog. 2076 private static final int FIND_HEIGHT = 79; 2077 2078 @Override 2079 protected int computeVerticalScrollRange() { 2080 if (mDrawHistory) { 2081 return mHistoryHeight; 2082 } else { 2083 // to avoid rounding error caused unnecessary scrollbar, use floor 2084 return (int) Math.floor(mContentHeight * mActualScale); 2085 } 2086 } 2087 2088 @Override 2089 protected int computeVerticalScrollOffset() { 2090 return Math.max(mScrollY - getTitleHeight(), 0); 2091 } 2092 2093 @Override 2094 protected int computeVerticalScrollExtent() { 2095 return getViewHeight(); 2096 } 2097 2098 /** @hide */ 2099 @Override 2100 protected void onDrawVerticalScrollBar(Canvas canvas, 2101 Drawable scrollBar, 2102 int l, int t, int r, int b) { 2103 scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b); 2104 scrollBar.draw(canvas); 2105 } 2106 2107 /** 2108 * Get the url for the current page. This is not always the same as the url 2109 * passed to WebViewClient.onPageStarted because although the load for 2110 * that url has begun, the current page may not have changed. 2111 * @return The url for the current page. 2112 */ 2113 public String getUrl() { 2114 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2115 return h != null ? h.getUrl() : null; 2116 } 2117 2118 /** 2119 * Get the original url for the current page. This is not always the same 2120 * as the url passed to WebViewClient.onPageStarted because although the 2121 * load for that url has begun, the current page may not have changed. 2122 * Also, there may have been redirects resulting in a different url to that 2123 * originally requested. 2124 * @return The url that was originally requested for the current page. 2125 */ 2126 public String getOriginalUrl() { 2127 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2128 return h != null ? h.getOriginalUrl() : null; 2129 } 2130 2131 /** 2132 * Get the title for the current page. This is the title of the current page 2133 * until WebViewClient.onReceivedTitle is called. 2134 * @return The title for the current page. 2135 */ 2136 public String getTitle() { 2137 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2138 return h != null ? h.getTitle() : null; 2139 } 2140 2141 /** 2142 * Get the favicon for the current page. This is the favicon of the current 2143 * page until WebViewClient.onReceivedIcon is called. 2144 * @return The favicon for the current page. 2145 */ 2146 public Bitmap getFavicon() { 2147 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2148 return h != null ? h.getFavicon() : null; 2149 } 2150 2151 /** 2152 * Get the touch icon url for the apple-touch-icon <link> element. 2153 * @hide 2154 */ 2155 public String getTouchIconUrl() { 2156 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2157 return h != null ? h.getTouchIconUrl() : null; 2158 } 2159 2160 /** 2161 * Get the progress for the current page. 2162 * @return The progress for the current page between 0 and 100. 2163 */ 2164 public int getProgress() { 2165 return mCallbackProxy.getProgress(); 2166 } 2167 2168 /** 2169 * @return the height of the HTML content. 2170 */ 2171 public int getContentHeight() { 2172 return mContentHeight; 2173 } 2174 2175 /** 2176 * Pause all layout, parsing, and javascript timers for all webviews. This 2177 * is a global requests, not restricted to just this webview. This can be 2178 * useful if the application has been paused. 2179 */ 2180 public void pauseTimers() { 2181 mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); 2182 } 2183 2184 /** 2185 * Resume all layout, parsing, and javascript timers for all webviews. 2186 * This will resume dispatching all timers. 2187 */ 2188 public void resumeTimers() { 2189 mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); 2190 } 2191 2192 /** 2193 * Call this to pause any extra processing associated with this view and 2194 * its associated DOM/plugins/javascript/etc. For example, if the view is 2195 * taken offscreen, this could be called to reduce unnecessary CPU and/or 2196 * network traffic. When the view is again "active", call onResume(). 2197 * 2198 * Note that this differs from pauseTimers(), which affects all views/DOMs 2199 * @hide 2200 */ 2201 public void onPause() { 2202 if (!mIsPaused) { 2203 mIsPaused = true; 2204 mWebViewCore.sendMessage(EventHub.ON_PAUSE); 2205 } 2206 } 2207 2208 /** 2209 * Call this to balanace a previous call to onPause() 2210 * @hide 2211 */ 2212 public void onResume() { 2213 if (mIsPaused) { 2214 mIsPaused = false; 2215 mWebViewCore.sendMessage(EventHub.ON_RESUME); 2216 } 2217 } 2218 2219 /** 2220 * Returns true if the view is paused, meaning onPause() was called. Calling 2221 * onResume() sets the paused state back to false. 2222 * @hide 2223 */ 2224 public boolean isPaused() { 2225 return mIsPaused; 2226 } 2227 2228 /** 2229 * Call this to inform the view that memory is low so that it can 2230 * free any available memory. 2231 * @hide 2232 */ 2233 public void freeMemory() { 2234 mWebViewCore.sendMessage(EventHub.FREE_MEMORY); 2235 } 2236 2237 /** 2238 * Clear the resource cache. Note that the cache is per-application, so 2239 * this will clear the cache for all WebViews used. 2240 * 2241 * @param includeDiskFiles If false, only the RAM cache is cleared. 2242 */ 2243 public void clearCache(boolean includeDiskFiles) { 2244 // Note: this really needs to be a static method as it clears cache for all 2245 // WebView. But we need mWebViewCore to send message to WebCore thread, so 2246 // we can't make this static. 2247 mWebViewCore.sendMessage(EventHub.CLEAR_CACHE, 2248 includeDiskFiles ? 1 : 0, 0); 2249 } 2250 2251 /** 2252 * Make sure that clearing the form data removes the adapter from the 2253 * currently focused textfield if there is one. 2254 */ 2255 public void clearFormData() { 2256 if (inEditingMode()) { 2257 AutoCompleteAdapter adapter = null; 2258 mWebTextView.setAdapterCustom(adapter); 2259 } 2260 } 2261 2262 /** 2263 * Tell the WebView to clear its internal back/forward list. 2264 */ 2265 public void clearHistory() { 2266 mCallbackProxy.getBackForwardList().setClearPending(); 2267 mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY); 2268 } 2269 2270 /** 2271 * Clear the SSL preferences table stored in response to proceeding with SSL 2272 * certificate errors. 2273 */ 2274 public void clearSslPreferences() { 2275 mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); 2276 } 2277 2278 /** 2279 * Return the WebBackForwardList for this WebView. This contains the 2280 * back/forward list for use in querying each item in the history stack. 2281 * This is a copy of the private WebBackForwardList so it contains only a 2282 * snapshot of the current state. Multiple calls to this method may return 2283 * different objects. The object returned from this method will not be 2284 * updated to reflect any new state. 2285 */ 2286 public WebBackForwardList copyBackForwardList() { 2287 return mCallbackProxy.getBackForwardList().clone(); 2288 } 2289 2290 /* 2291 * Highlight and scroll to the next occurance of String in findAll. 2292 * Wraps the page infinitely, and scrolls. Must be called after 2293 * calling findAll. 2294 * 2295 * @param forward Direction to search. 2296 */ 2297 public void findNext(boolean forward) { 2298 nativeFindNext(forward); 2299 } 2300 2301 /* 2302 * Find all instances of find on the page and highlight them. 2303 * @param find String to find. 2304 * @return int The number of occurances of the String "find" 2305 * that were found. 2306 */ 2307 public int findAll(String find) { 2308 mFindIsUp = true; 2309 int result = nativeFindAll(find.toLowerCase(), find.toUpperCase()); 2310 invalidate(); 2311 return result; 2312 } 2313 2314 // Used to know whether the find dialog is open. Affects whether 2315 // or not we draw the highlights for matches. 2316 private boolean mFindIsUp; 2317 2318 /** 2319 * Return the first substring consisting of the address of a physical 2320 * location. Currently, only addresses in the United States are detected, 2321 * and consist of: 2322 * - a house number 2323 * - a street name 2324 * - a street type (Road, Circle, etc), either spelled out or abbreviated 2325 * - a city name 2326 * - a state or territory, either spelled out or two-letter abbr. 2327 * - an optional 5 digit or 9 digit zip code. 2328 * 2329 * All names must be correctly capitalized, and the zip code, if present, 2330 * must be valid for the state. The street type must be a standard USPS 2331 * spelling or abbreviation. The state or territory must also be spelled 2332 * or abbreviated using USPS standards. The house number may not exceed 2333 * five digits. 2334 * @param addr The string to search for addresses. 2335 * 2336 * @return the address, or if no address is found, return null. 2337 */ 2338 public static String findAddress(String addr) { 2339 return findAddress(addr, false); 2340 } 2341 2342 /** 2343 * @hide 2344 * Return the first substring consisting of the address of a physical 2345 * location. Currently, only addresses in the United States are detected, 2346 * and consist of: 2347 * - a house number 2348 * - a street name 2349 * - a street type (Road, Circle, etc), either spelled out or abbreviated 2350 * - a city name 2351 * - a state or territory, either spelled out or two-letter abbr. 2352 * - an optional 5 digit or 9 digit zip code. 2353 * 2354 * Names are optionally capitalized, and the zip code, if present, 2355 * must be valid for the state. The street type must be a standard USPS 2356 * spelling or abbreviation. The state or territory must also be spelled 2357 * or abbreviated using USPS standards. The house number may not exceed 2358 * five digits. 2359 * @param addr The string to search for addresses. 2360 * @param caseInsensitive addr Set to true to make search ignore case. 2361 * 2362 * @return the address, or if no address is found, return null. 2363 */ 2364 public static String findAddress(String addr, boolean caseInsensitive) { 2365 return WebViewCore.nativeFindAddress(addr, caseInsensitive); 2366 } 2367 2368 /* 2369 * Clear the highlighting surrounding text matches created by findAll. 2370 */ 2371 public void clearMatches() { 2372 mFindIsUp = false; 2373 nativeSetFindIsDown(); 2374 // Now that the dialog has been removed, ensure that we scroll to a 2375 // location that is not beyond the end of the page. 2376 pinScrollTo(mScrollX, mScrollY, false, 0); 2377 invalidate(); 2378 } 2379 2380 /** 2381 * Query the document to see if it contains any image references. The 2382 * message object will be dispatched with arg1 being set to 1 if images 2383 * were found and 0 if the document does not reference any images. 2384 * @param response The message that will be dispatched with the result. 2385 */ 2386 public void documentHasImages(Message response) { 2387 if (response == null) { 2388 return; 2389 } 2390 mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response); 2391 } 2392 2393 @Override 2394 public void computeScroll() { 2395 if (mScroller.computeScrollOffset()) { 2396 int oldX = mScrollX; 2397 int oldY = mScrollY; 2398 mScrollX = mScroller.getCurrX(); 2399 mScrollY = mScroller.getCurrY(); 2400 postInvalidate(); // So we draw again 2401 if (oldX != mScrollX || oldY != mScrollY) { 2402 // as onScrollChanged() is not called, sendOurVisibleRect() 2403 // needs to be call explicitly 2404 sendOurVisibleRect(); 2405 } 2406 } else { 2407 super.computeScroll(); 2408 } 2409 } 2410 2411 private static int computeDuration(int dx, int dy) { 2412 int distance = Math.max(Math.abs(dx), Math.abs(dy)); 2413 int duration = distance * 1000 / STD_SPEED; 2414 return Math.min(duration, MAX_DURATION); 2415 } 2416 2417 // helper to pin the scrollBy parameters (already in view coordinates) 2418 // returns true if the scroll was changed 2419 private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) { 2420 return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration); 2421 } 2422 2423 // helper to pin the scrollTo parameters (already in view coordinates) 2424 // returns true if the scroll was changed 2425 private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) { 2426 x = pinLocX(x); 2427 y = pinLocY(y); 2428 int dx = x - mScrollX; 2429 int dy = y - mScrollY; 2430 2431 if ((dx | dy) == 0) { 2432 return false; 2433 } 2434 // By this point we have added in the title bar's height. If the site 2435 // is trying to scroll to the top of the page, scroll it to the top 2436 // of the WebView including showing the title bar. 2437 // mobile sites prefer to scroll to (0, 1), thus the + 1 below 2438 if (getVisibleTitleHeight() > 0 && x == 0 2439 && y <= getTitleHeight() + 1) { 2440 y = 0; 2441 animate = false; 2442 } 2443 if (animate) { 2444 // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); 2445 mScroller.startScroll(mScrollX, mScrollY, dx, dy, 2446 animationDuration > 0 ? animationDuration : computeDuration(dx, dy)); 2447 invalidate(); 2448 } else { 2449 abortAnimation(); // just in case 2450 scrollTo(x, y); 2451 } 2452 return true; 2453 } 2454 2455 // Scale from content to view coordinates, and pin. 2456 // Also called by jni webview.cpp 2457 private boolean setContentScrollBy(int cx, int cy, boolean animate) { 2458 if (mDrawHistory) { 2459 // disallow WebView to change the scroll position as History Picture 2460 // is used in the view system. 2461 // TODO: as we switchOutDrawHistory when trackball or navigation 2462 // keys are hit, this should be safe. Right? 2463 return false; 2464 } 2465 cx = contentToViewDimension(cx); 2466 cy = contentToViewDimension(cy); 2467 if (mHeightCanMeasure) { 2468 // move our visible rect according to scroll request 2469 if (cy != 0) { 2470 Rect tempRect = new Rect(); 2471 calcOurVisibleRect(tempRect); 2472 tempRect.offset(cx, cy); 2473 requestRectangleOnScreen(tempRect); 2474 } 2475 // FIXME: We scroll horizontally no matter what because currently 2476 // ScrollView and ListView will not scroll horizontally. 2477 // FIXME: Why do we only scroll horizontally if there is no 2478 // vertical scroll? 2479// Log.d(LOGTAG, "setContentScrollBy cy=" + cy); 2480 return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0); 2481 } else { 2482 return pinScrollBy(cx, cy, animate, 0); 2483 } 2484 } 2485 2486 // scale from content to view coordinates, and pin 2487 // return true if pin caused the final x/y different than the request cx/cy, 2488 // and a future scroll may reach the request cx/cy after our size has 2489 // changed 2490 // return false if the view scroll to the exact position as it is requested, 2491 // where negative numbers are taken to mean 0 2492 private boolean setContentScrollTo(int cx, int cy) { 2493 if (mDrawHistory) { 2494 // disallow WebView to change the scroll position as History Picture 2495 // is used in the view system. 2496 // One known case where this is called is that WebCore tries to 2497 // restore the scroll position. As history Picture already uses the 2498 // saved scroll position, it is ok to skip this. 2499 return false; 2500 } 2501 int vx = contentToViewX(cx); 2502 int vy = contentToViewY(cy); 2503// Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" + 2504// vx + " " + vy + "]"); 2505 pinScrollTo(vx, vy, false, 0); 2506 // If the request was to scroll to a negative coordinate, treat it as if 2507 // it was a request to scroll to 0 2508 if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) { 2509 return true; 2510 } else { 2511 return false; 2512 } 2513 } 2514 2515 // scale from content to view coordinates, and pin 2516 private void spawnContentScrollTo(int cx, int cy) { 2517 if (mDrawHistory) { 2518 // disallow WebView to change the scroll position as History Picture 2519 // is used in the view system. 2520 return; 2521 } 2522 int vx = contentToViewX(cx); 2523 int vy = contentToViewY(cy); 2524 pinScrollTo(vx, vy, true, 0); 2525 } 2526 2527 /** 2528 * These are from webkit, and are in content coordinate system (unzoomed) 2529 */ 2530 private void contentSizeChanged(boolean updateLayout) { 2531 // suppress 0,0 since we usually see real dimensions soon after 2532 // this avoids drawing the prev content in a funny place. If we find a 2533 // way to consolidate these notifications, this check may become 2534 // obsolete 2535 if ((mContentWidth | mContentHeight) == 0) { 2536 return; 2537 } 2538 2539 if (mHeightCanMeasure) { 2540 if (getMeasuredHeight() != contentToViewDimension(mContentHeight) 2541 && updateLayout) { 2542 requestLayout(); 2543 } 2544 } else if (mWidthCanMeasure) { 2545 if (getMeasuredWidth() != contentToViewDimension(mContentWidth) 2546 && updateLayout) { 2547 requestLayout(); 2548 } 2549 } else { 2550 // If we don't request a layout, try to send our view size to the 2551 // native side to ensure that WebCore has the correct dimensions. 2552 sendViewSizeZoom(); 2553 } 2554 } 2555 2556 /** 2557 * Set the WebViewClient that will receive various notifications and 2558 * requests. This will replace the current handler. 2559 * @param client An implementation of WebViewClient. 2560 */ 2561 public void setWebViewClient(WebViewClient client) { 2562 mCallbackProxy.setWebViewClient(client); 2563 } 2564 2565 /** 2566 * Register the interface to be used when content can not be handled by 2567 * the rendering engine, and should be downloaded instead. This will replace 2568 * the current handler. 2569 * @param listener An implementation of DownloadListener. 2570 */ 2571 public void setDownloadListener(DownloadListener listener) { 2572 mCallbackProxy.setDownloadListener(listener); 2573 } 2574 2575 /** 2576 * Set the chrome handler. This is an implementation of WebChromeClient for 2577 * use in handling Javascript dialogs, favicons, titles, and the progress. 2578 * This will replace the current handler. 2579 * @param client An implementation of WebChromeClient. 2580 */ 2581 public void setWebChromeClient(WebChromeClient client) { 2582 mCallbackProxy.setWebChromeClient(client); 2583 } 2584 2585 /** 2586 * Gets the chrome handler. 2587 * @return the current WebChromeClient instance. 2588 * 2589 * @hide API council approval. 2590 */ 2591 public WebChromeClient getWebChromeClient() { 2592 return mCallbackProxy.getWebChromeClient(); 2593 } 2594 2595 /** 2596 * Set the Picture listener. This is an interface used to receive 2597 * notifications of a new Picture. 2598 * @param listener An implementation of WebView.PictureListener. 2599 */ 2600 public void setPictureListener(PictureListener listener) { 2601 mPictureListener = listener; 2602 } 2603 2604 /** 2605 * {@hide} 2606 */ 2607 /* FIXME: Debug only! Remove for SDK! */ 2608 public void externalRepresentation(Message callback) { 2609 mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback); 2610 } 2611 2612 /** 2613 * {@hide} 2614 */ 2615 /* FIXME: Debug only! Remove for SDK! */ 2616 public void documentAsText(Message callback) { 2617 mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback); 2618 } 2619 2620 /** 2621 * Use this function to bind an object to Javascript so that the 2622 * methods can be accessed from Javascript. 2623 * <p><strong>IMPORTANT:</strong> 2624 * <ul> 2625 * <li> Using addJavascriptInterface() allows JavaScript to control your 2626 * application. This can be a very useful feature or a dangerous security 2627 * issue. When the HTML in the WebView is untrustworthy (for example, part 2628 * or all of the HTML is provided by some person or process), then an 2629 * attacker could inject HTML that will execute your code and possibly any 2630 * code of the attacker's choosing.<br> 2631 * Do not use addJavascriptInterface() unless all of the HTML in this 2632 * WebView was written by you.</li> 2633 * <li> The Java object that is bound runs in another thread and not in 2634 * the thread that it was constructed in.</li> 2635 * </ul></p> 2636 * @param obj The class instance to bind to Javascript 2637 * @param interfaceName The name to used to expose the class in Javascript 2638 */ 2639 public void addJavascriptInterface(Object obj, String interfaceName) { 2640 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); 2641 arg.mObject = obj; 2642 arg.mInterfaceName = interfaceName; 2643 mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); 2644 } 2645 2646 /** 2647 * Return the WebSettings object used to control the settings for this 2648 * WebView. 2649 * @return A WebSettings object that can be used to control this WebView's 2650 * settings. 2651 */ 2652 public WebSettings getSettings() { 2653 return mWebViewCore.getSettings(); 2654 } 2655 2656 /** 2657 * Return the list of currently loaded plugins. 2658 * @return The list of currently loaded plugins. 2659 * 2660 * @deprecated This was used for Gears, which has been deprecated. 2661 */ 2662 @Deprecated 2663 public static synchronized PluginList getPluginList() { 2664 return null; 2665 } 2666 2667 /** 2668 * @deprecated This was used for Gears, which has been deprecated. 2669 */ 2670 @Deprecated 2671 public void refreshPlugins(boolean reloadOpenPages) { } 2672 2673 //------------------------------------------------------------------------- 2674 // Override View methods 2675 //------------------------------------------------------------------------- 2676 2677 @Override 2678 protected void finalize() throws Throwable { 2679 try { 2680 destroy(); 2681 } finally { 2682 super.finalize(); 2683 } 2684 } 2685 2686 @Override 2687 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 2688 if (child == mTitleBar) { 2689 // When drawing the title bar, move it horizontally to always show 2690 // at the top of the WebView. 2691 mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft()); 2692 } 2693 return super.drawChild(canvas, child, drawingTime); 2694 } 2695 2696 @Override 2697 protected void onDraw(Canvas canvas) { 2698 // if mNativeClass is 0, the WebView has been destroyed. Do nothing. 2699 if (mNativeClass == 0) { 2700 return; 2701 } 2702 int saveCount = canvas.save(); 2703 if (mTitleBar != null) { 2704 canvas.translate(0, (int) mTitleBar.getHeight()); 2705 } 2706 // Update the buttons in the picture, so when we draw the picture 2707 // to the screen, they are in the correct state. 2708 // Tell the native side if user is a) touching the screen, 2709 // b) pressing the trackball down, or c) pressing the enter key 2710 // If the cursor is on a button, we need to draw it in the pressed 2711 // state. 2712 // If mNativeClass is 0, we should not reach here, so we do not 2713 // need to check it again. 2714 nativeRecordButtons(hasFocus() && hasWindowFocus(), 2715 mTouchMode == TOUCH_SHORTPRESS_START_MODE 2716 || mTrackballDown || mGotCenterDown, false); 2717 drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); 2718 canvas.restoreToCount(saveCount); 2719 2720 // Now draw the shadow. 2721 if (mTitleBar != null) { 2722 int y = mScrollY + getVisibleTitleHeight(); 2723 int height = (int) (5f * getContext().getResources() 2724 .getDisplayMetrics().density); 2725 mTitleShadow.setBounds(mScrollX, y, mScrollX + getWidth(), 2726 y + height); 2727 mTitleShadow.draw(canvas); 2728 } 2729 if (AUTO_REDRAW_HACK && mAutoRedraw) { 2730 invalidate(); 2731 } 2732 } 2733 2734 @Override 2735 public void setLayoutParams(ViewGroup.LayoutParams params) { 2736 if (params.height == LayoutParams.WRAP_CONTENT) { 2737 mWrapContent = true; 2738 } 2739 super.setLayoutParams(params); 2740 } 2741 2742 @Override 2743 public boolean performLongClick() { 2744 if (mNativeClass != 0 && nativeCursorIsTextInput()) { 2745 // Send the click so that the textfield is in focus 2746 // FIXME: When we start respecting changes to the native textfield's 2747 // selection, need to make sure that this does not change it. 2748 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 2749 nativeCursorNodePointer()); 2750 rebuildWebTextView(); 2751 } 2752 if (inEditingMode()) { 2753 return mWebTextView.performLongClick(); 2754 } else { 2755 return super.performLongClick(); 2756 } 2757 } 2758 2759 /** 2760 * Need to adjust the WebTextView after a change in zoom, since mActualScale 2761 * has changed. This is especially important for password fields, which are 2762 * drawn by the WebTextView, since it conveys more information than what 2763 * webkit draws. Thus we need to reposition it to show in the correct 2764 * place. 2765 */ 2766 private boolean mNeedToAdjustWebTextView; 2767 2768 private void drawCoreAndCursorRing(Canvas canvas, int color, 2769 boolean drawCursorRing) { 2770 if (mDrawHistory) { 2771 canvas.scale(mActualScale, mActualScale); 2772 canvas.drawPicture(mHistoryPicture); 2773 return; 2774 } 2775 2776 boolean animateZoom = mZoomScale != 0; 2777 boolean animateScroll = !mScroller.isFinished() 2778 || mVelocityTracker != null; 2779 if (animateZoom) { 2780 float zoomScale; 2781 int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); 2782 if (interval < ZOOM_ANIMATION_LENGTH) { 2783 float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; 2784 zoomScale = 1.0f / (mInvInitialZoomScale 2785 + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); 2786 invalidate(); 2787 } else { 2788 zoomScale = mZoomScale; 2789 // set mZoomScale to be 0 as we have done animation 2790 mZoomScale = 0; 2791 // call invalidate() again to draw with the final filters 2792 invalidate(); 2793 if (mNeedToAdjustWebTextView) { 2794 mNeedToAdjustWebTextView = false; 2795 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 2796 contentToViewDimension( 2797 nativeFocusCandidateTextSize())); 2798 Rect bounds = nativeFocusCandidateNodeBounds(); 2799 Rect vBox = contentToViewRect(bounds); 2800 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), 2801 vBox.height()); 2802 // If it is a password field, start drawing the 2803 // WebTextView once again. 2804 if (nativeFocusCandidateIsPassword()) { 2805 mWebTextView.setInPassword(true); 2806 } 2807 } 2808 } 2809 // calculate the intermediate scroll position. As we need to use 2810 // zoomScale, we can't use pinLocX/Y directly. Copy the logic here. 2811 float scale = zoomScale * mInvInitialZoomScale; 2812 int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) 2813 - mZoomCenterX); 2814 tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth 2815 * zoomScale)) + mScrollX; 2816 int titleHeight = getTitleHeight(); 2817 int ty = Math.round(scale 2818 * (mInitialScrollY + mZoomCenterY - titleHeight) 2819 - (mZoomCenterY - titleHeight)); 2820 ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty 2821 - titleHeight, getViewHeight(), Math.round(mContentHeight 2822 * zoomScale)) + titleHeight) + mScrollY; 2823 canvas.translate(tx, ty); 2824 canvas.scale(zoomScale, zoomScale); 2825 if (inEditingMode() && !mNeedToAdjustWebTextView 2826 && mZoomScale != 0) { 2827 // The WebTextView is up. Keep track of this so we can adjust 2828 // its size and placement when we finish zooming 2829 mNeedToAdjustWebTextView = true; 2830 // If it is in password mode, turn it off so it does not draw 2831 // misplaced. 2832 if (nativeFocusCandidateIsPassword()) { 2833 mWebTextView.setInPassword(false); 2834 } 2835 } 2836 } else { 2837 canvas.scale(mActualScale, mActualScale); 2838 } 2839 2840 mWebViewCore.drawContentPicture(canvas, color, animateZoom, 2841 animateScroll); 2842 2843 if (mNativeClass == 0) return; 2844 if (mShiftIsPressed) { 2845 if (mTouchSelection) { 2846 nativeDrawSelectionRegion(canvas); 2847 } else { 2848 nativeDrawSelection(canvas, mSelectX, mSelectY, 2849 mExtendSelection); 2850 } 2851 } else if (drawCursorRing) { 2852 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 2853 mTouchMode = TOUCH_SHORTPRESS_MODE; 2854 HitTestResult hitTest = getHitTestResult(); 2855 if (hitTest != null && 2856 hitTest.mType != HitTestResult.UNKNOWN_TYPE) { 2857 mPrivateHandler.sendMessageDelayed(mPrivateHandler 2858 .obtainMessage(SWITCH_TO_LONGPRESS), 2859 LONG_PRESS_TIMEOUT); 2860 } 2861 } 2862 nativeDrawCursorRing(canvas); 2863 } 2864 // When the FindDialog is up, only draw the matches if we are not in 2865 // the process of scrolling them into view. 2866 if (mFindIsUp && !animateScroll) { 2867 nativeDrawMatches(canvas); 2868 } 2869 } 2870 2871 // draw history 2872 private boolean mDrawHistory = false; 2873 private Picture mHistoryPicture = null; 2874 private int mHistoryWidth = 0; 2875 private int mHistoryHeight = 0; 2876 2877 // Only check the flag, can be called from WebCore thread 2878 boolean drawHistory() { 2879 return mDrawHistory; 2880 } 2881 2882 // Should only be called in UI thread 2883 void switchOutDrawHistory() { 2884 if (null == mWebViewCore) return; // CallbackProxy may trigger this 2885 if (mDrawHistory && mWebViewCore.pictureReady()) { 2886 mDrawHistory = false; 2887 invalidate(); 2888 int oldScrollX = mScrollX; 2889 int oldScrollY = mScrollY; 2890 mScrollX = pinLocX(mScrollX); 2891 mScrollY = pinLocY(mScrollY); 2892 if (oldScrollX != mScrollX || oldScrollY != mScrollY) { 2893 mUserScroll = false; 2894 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX, 2895 oldScrollY); 2896 } 2897 sendOurVisibleRect(); 2898 } 2899 } 2900 2901 WebViewCore.CursorData cursorData() { 2902 WebViewCore.CursorData result = new WebViewCore.CursorData(); 2903 result.mMoveGeneration = nativeMoveGeneration(); 2904 result.mFrame = nativeCursorFramePointer(); 2905 Point position = nativeCursorPosition(); 2906 result.mX = position.x; 2907 result.mY = position.y; 2908 return result; 2909 } 2910 2911 /** 2912 * Delete text from start to end in the focused textfield. If there is no 2913 * focus, or if start == end, silently fail. If start and end are out of 2914 * order, swap them. 2915 * @param start Beginning of selection to delete. 2916 * @param end End of selection to delete. 2917 */ 2918 /* package */ void deleteSelection(int start, int end) { 2919 mTextGeneration++; 2920 WebViewCore.TextSelectionData data 2921 = new WebViewCore.TextSelectionData(start, end); 2922 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0, 2923 data); 2924 } 2925 2926 /** 2927 * Set the selection to (start, end) in the focused textfield. If start and 2928 * end are out of order, swap them. 2929 * @param start Beginning of selection. 2930 * @param end End of selection. 2931 */ 2932 /* package */ void setSelection(int start, int end) { 2933 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end); 2934 } 2935 2936 // Called by JNI when a touch event puts a textfield into focus. 2937 private void displaySoftKeyboard(boolean isTextView) { 2938 InputMethodManager imm = (InputMethodManager) 2939 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 2940 2941 if (isTextView) { 2942 if (mWebTextView == null) return; 2943 2944 imm.showSoftInput(mWebTextView, 0); 2945 // Now we need to fake a touch event to place the cursor where the 2946 // user touched. 2947 AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) 2948 mWebTextView.getLayoutParams(); 2949 if (lp != null) { 2950 // Take the last touch and adjust for the location of the 2951 // WebTextView. 2952 float x = mLastTouchX + (float) (mScrollX - lp.x); 2953 float y = mLastTouchY + (float) (mScrollY - lp.y); 2954 mWebTextView.fakeTouchEvent(x, y); 2955 } 2956 if (mInZoomOverview) { 2957 // if in zoom overview mode, call doDoubleTap() to bring it back 2958 // to normal mode so that user can enter text. 2959 doDoubleTap(); 2960 } 2961 } 2962 else { // used by plugins 2963 imm.showSoftInput(this, 0); 2964 } 2965 } 2966 2967 // Called by WebKit to instruct the UI to hide the keyboard 2968 private void hideSoftKeyboard() { 2969 InputMethodManager imm = (InputMethodManager) 2970 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 2971 2972 imm.hideSoftInputFromWindow(this.getWindowToken(), 0); 2973 } 2974 2975 /* 2976 * This method checks the current focus and cursor and potentially rebuilds 2977 * mWebTextView to have the appropriate properties, such as password, 2978 * multiline, and what text it contains. It also removes it if necessary. 2979 */ 2980 /* package */ void rebuildWebTextView() { 2981 // If the WebView does not have focus, do nothing until it gains focus. 2982 if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) { 2983 return; 2984 } 2985 boolean alreadyThere = inEditingMode(); 2986 // inEditingMode can only return true if mWebTextView is non-null, 2987 // so we can safely call remove() if (alreadyThere) 2988 if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) { 2989 if (alreadyThere) { 2990 mWebTextView.remove(); 2991 } 2992 return; 2993 } 2994 // At this point, we know we have found an input field, so go ahead 2995 // and create the WebTextView if necessary. 2996 if (mWebTextView == null) { 2997 mWebTextView = new WebTextView(mContext, WebView.this); 2998 // Initialize our generation number. 2999 mTextGeneration = 0; 3000 } 3001 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 3002 contentToViewDimension(nativeFocusCandidateTextSize())); 3003 Rect visibleRect = new Rect(); 3004 calcOurContentVisibleRect(visibleRect); 3005 // Note that sendOurVisibleRect calls viewToContent, so the coordinates 3006 // should be in content coordinates. 3007 Rect bounds = nativeFocusCandidateNodeBounds(); 3008 if (!Rect.intersects(bounds, visibleRect)) { 3009 mWebTextView.bringIntoView(); 3010 } 3011 String text = nativeFocusCandidateText(); 3012 int nodePointer = nativeFocusCandidatePointer(); 3013 if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) { 3014 // It is possible that we have the same textfield, but it has moved, 3015 // i.e. In the case of opening/closing the screen. 3016 // In that case, we need to set the dimensions, but not the other 3017 // aspects. 3018 // We also need to restore the selection, which gets wrecked by 3019 // calling setTextEntryRect. 3020 Spannable spannable = (Spannable) mWebTextView.getText(); 3021 int start = Selection.getSelectionStart(spannable); 3022 int end = Selection.getSelectionEnd(spannable); 3023 // If the text has been changed by webkit, update it. However, if 3024 // there has been more UI text input, ignore it. We will receive 3025 // another update when that text is recognized. 3026 if (text != null && !text.equals(spannable.toString()) 3027 && nativeTextGeneration() == mTextGeneration) { 3028 mWebTextView.setTextAndKeepSelection(text); 3029 } else { 3030 Selection.setSelection(spannable, start, end); 3031 } 3032 } else { 3033 Rect vBox = contentToViewRect(bounds); 3034 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), 3035 vBox.height()); 3036 mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ? 3037 Gravity.RIGHT : Gravity.NO_GRAVITY); 3038 // this needs to be called before update adapter thread starts to 3039 // ensure the mWebTextView has the same node pointer 3040 mWebTextView.setNodePointer(nodePointer); 3041 int maxLength = -1; 3042 boolean isTextField = nativeFocusCandidateIsTextField(); 3043 if (isTextField) { 3044 maxLength = nativeFocusCandidateMaxLength(); 3045 String name = nativeFocusCandidateName(); 3046 if (mWebViewCore.getSettings().getSaveFormData() 3047 && name != null) { 3048 Message update = mPrivateHandler.obtainMessage( 3049 REQUEST_FORM_DATA, nodePointer); 3050 RequestFormData updater = new RequestFormData(name, 3051 getUrl(), update); 3052 Thread t = new Thread(updater); 3053 t.start(); 3054 } 3055 } 3056 mWebTextView.setMaxLength(maxLength); 3057 AutoCompleteAdapter adapter = null; 3058 mWebTextView.setAdapterCustom(adapter); 3059 mWebTextView.setSingleLine(isTextField); 3060 mWebTextView.setInPassword(nativeFocusCandidateIsPassword()); 3061 if (null == text) { 3062 mWebTextView.setText("", 0, 0); 3063 if (DebugFlags.WEB_VIEW) { 3064 Log.v(LOGTAG, "rebuildWebTextView null == text"); 3065 } 3066 } else { 3067 // Change to true to enable the old style behavior, where 3068 // entering a textfield/textarea always set the selection to the 3069 // whole field. This was desirable for the case where the user 3070 // intends to scroll past the field using the trackball. 3071 // However, it causes a problem when replying to emails - the 3072 // user expects the cursor to be at the beginning of the 3073 // textarea. Testing out a new behavior, where textfields set 3074 // selection at the end, and textareas at the beginning. 3075 if (false) { 3076 mWebTextView.setText(text, 0, text.length()); 3077 } else if (isTextField) { 3078 int length = text.length(); 3079 mWebTextView.setText(text, length, length); 3080 if (DebugFlags.WEB_VIEW) { 3081 Log.v(LOGTAG, "rebuildWebTextView length=" + length); 3082 } 3083 } else { 3084 mWebTextView.setText(text, 0, 0); 3085 if (DebugFlags.WEB_VIEW) { 3086 Log.v(LOGTAG, "rebuildWebTextView !isTextField"); 3087 } 3088 } 3089 } 3090 mWebTextView.requestFocus(); 3091 } 3092 } 3093 3094 /* 3095 * This class requests an Adapter for the WebTextView which shows past 3096 * entries stored in the database. It is a Runnable so that it can be done 3097 * in its own thread, without slowing down the UI. 3098 */ 3099 private class RequestFormData implements Runnable { 3100 private String mName; 3101 private String mUrl; 3102 private Message mUpdateMessage; 3103 3104 public RequestFormData(String name, String url, Message msg) { 3105 mName = name; 3106 mUrl = url; 3107 mUpdateMessage = msg; 3108 } 3109 3110 public void run() { 3111 ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName); 3112 if (pastEntries.size() > 0) { 3113 AutoCompleteAdapter adapter = new 3114 AutoCompleteAdapter(mContext, pastEntries); 3115 mUpdateMessage.obj = adapter; 3116 mUpdateMessage.sendToTarget(); 3117 } 3118 } 3119 } 3120 3121 // This is used to determine long press with the center key. Does not 3122 // affect long press with the trackball/touch. 3123 private boolean mGotCenterDown = false; 3124 3125 @Override 3126 public boolean onKeyDown(int keyCode, KeyEvent event) { 3127 if (DebugFlags.WEB_VIEW) { 3128 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() 3129 + ", " + event + ", unicode=" + event.getUnicodeChar()); 3130 } 3131 3132 if (mNativeClass == 0) { 3133 return false; 3134 } 3135 3136 // do this hack up front, so it always works, regardless of touch-mode 3137 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) { 3138 mAutoRedraw = !mAutoRedraw; 3139 if (mAutoRedraw) { 3140 invalidate(); 3141 } 3142 return true; 3143 } 3144 3145 // Bubble up the key event if 3146 // 1. it is a system key; or 3147 // 2. the host application wants to handle it; 3148 if (event.isSystem() 3149 || mCallbackProxy.uiOverrideKeyEvent(event)) { 3150 return false; 3151 } 3152 3153 if (mShiftIsPressed == false && nativeCursorWantsKeyEvents() == false 3154 && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 3155 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { 3156 mExtendSelection = false; 3157 mShiftIsPressed = true; 3158 if (nativeHasCursorNode()) { 3159 Rect rect = nativeCursorNodeBounds(); 3160 mSelectX = contentToViewX(rect.left); 3161 mSelectY = contentToViewY(rect.top); 3162 } else { 3163 mSelectX = mScrollX + (int) mLastTouchX; 3164 mSelectY = mScrollY + (int) mLastTouchY; 3165 } 3166 nativeHideCursor(); 3167 } 3168 3169 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 3170 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 3171 // always handle the navigation keys in the UI thread 3172 switchOutDrawHistory(); 3173 if (navHandledKey(keyCode, 1, false, event.getEventTime(), false)) { 3174 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 3175 return true; 3176 } 3177 // Bubble up the key event as WebView doesn't handle it 3178 return false; 3179 } 3180 3181 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 3182 switchOutDrawHistory(); 3183 if (event.getRepeatCount() == 0) { 3184 mGotCenterDown = true; 3185 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3186 .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); 3187 // Already checked mNativeClass, so we do not need to check it 3188 // again. 3189 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 3190 return true; 3191 } 3192 // Bubble up the key event as WebView doesn't handle it 3193 return false; 3194 } 3195 3196 if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT 3197 && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) { 3198 // turn off copy select if a shift-key combo is pressed 3199 mExtendSelection = mShiftIsPressed = false; 3200 if (mTouchMode == TOUCH_SELECT_MODE) { 3201 mTouchMode = TOUCH_INIT_MODE; 3202 } 3203 } 3204 3205 if (getSettings().getNavDump()) { 3206 switch (keyCode) { 3207 case KeyEvent.KEYCODE_4: 3208 // "/data/data/com.android.browser/displayTree.txt" 3209 nativeDumpDisplayTree(getUrl()); 3210 break; 3211 case KeyEvent.KEYCODE_5: 3212 case KeyEvent.KEYCODE_6: 3213 // 5: dump the dom tree to the file 3214 // "/data/data/com.android.browser/domTree.txt" 3215 // 6: dump the dom tree to the adb log 3216 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, 3217 (keyCode == KeyEvent.KEYCODE_5) ? 1 : 0, 0); 3218 break; 3219 case KeyEvent.KEYCODE_7: 3220 case KeyEvent.KEYCODE_8: 3221 // 7: dump the render tree to the file 3222 // "/data/data/com.android.browser/renderTree.txt" 3223 // 8: dump the render tree to the adb log 3224 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, 3225 (keyCode == KeyEvent.KEYCODE_7) ? 1 : 0, 0); 3226 break; 3227 case KeyEvent.KEYCODE_9: 3228 nativeInstrumentReport(); 3229 return true; 3230 } 3231 } 3232 3233 if (nativeCursorIsPlugin()) { 3234 nativeUpdatePluginReceivesEvents(); 3235 invalidate(); 3236 } else if (nativeCursorIsTextInput()) { 3237 // This message will put the node in focus, for the DOM's notion 3238 // of focus, and make the focuscontroller active 3239 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 3240 nativeCursorNodePointer()); 3241 // This will bring up the WebTextView and put it in focus, for 3242 // our view system's notion of focus 3243 rebuildWebTextView(); 3244 // Now we need to pass the event to it 3245 return mWebTextView.onKeyDown(keyCode, event); 3246 } else if (nativeHasFocusNode()) { 3247 // In this case, the cursor is not on a text input, but the focus 3248 // might be. Check it, and if so, hand over to the WebTextView. 3249 rebuildWebTextView(); 3250 if (inEditingMode()) { 3251 return mWebTextView.onKeyDown(keyCode, event); 3252 } 3253 } 3254 3255 // TODO: should we pass all the keys to DOM or check the meta tag 3256 if (nativeCursorWantsKeyEvents() || true) { 3257 // pass the key to DOM 3258 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 3259 // return true as DOM handles the key 3260 return true; 3261 } 3262 3263 // Bubble up the key event as WebView doesn't handle it 3264 return false; 3265 } 3266 3267 @Override 3268 public boolean onKeyUp(int keyCode, KeyEvent event) { 3269 if (DebugFlags.WEB_VIEW) { 3270 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() 3271 + ", " + event + ", unicode=" + event.getUnicodeChar()); 3272 } 3273 3274 if (mNativeClass == 0) { 3275 return false; 3276 } 3277 3278 // special CALL handling when cursor node's href is "tel:XXX" 3279 if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) { 3280 String text = nativeCursorText(); 3281 if (!nativeCursorIsTextInput() && text != null 3282 && text.startsWith(SCHEME_TEL)) { 3283 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); 3284 getContext().startActivity(intent); 3285 return true; 3286 } 3287 } 3288 3289 // Bubble up the key event if 3290 // 1. it is a system key; or 3291 // 2. the host application wants to handle it; 3292 if (event.isSystem() || mCallbackProxy.uiOverrideKeyEvent(event)) { 3293 return false; 3294 } 3295 3296 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 3297 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 3298 if (commitCopy()) { 3299 return true; 3300 } 3301 } 3302 3303 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 3304 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 3305 // always handle the navigation keys in the UI thread 3306 // Bubble up the key event as WebView doesn't handle it 3307 return false; 3308 } 3309 3310 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 3311 // remove the long press message first 3312 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 3313 mGotCenterDown = false; 3314 3315 if (mShiftIsPressed) { 3316 return false; 3317 } 3318 3319 // perform the single click 3320 Rect visibleRect = sendOurVisibleRect(); 3321 // Note that sendOurVisibleRect calls viewToContent, so the 3322 // coordinates should be in content coordinates. 3323 if (!nativeCursorIntersects(visibleRect)) { 3324 return false; 3325 } 3326 nativeSetFollowedLink(true); 3327 nativeUpdatePluginReceivesEvents(); 3328 WebViewCore.CursorData data = cursorData(); 3329 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); 3330 playSoundEffect(SoundEffectConstants.CLICK); 3331 boolean isTextInput = nativeCursorIsTextInput(); 3332 if (isTextInput || !mCallbackProxy.uiOverrideUrlLoading( 3333 nativeCursorText())) { 3334 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, 3335 nativeCursorNodePointer()); 3336 } 3337 if (isTextInput) { 3338 rebuildWebTextView(); 3339 } 3340 return true; 3341 } 3342 3343 // TODO: should we pass all the keys to DOM or check the meta tag 3344 if (nativeCursorWantsKeyEvents() || true) { 3345 // pass the key to DOM 3346 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 3347 // return true as DOM handles the key 3348 return true; 3349 } 3350 3351 // Bubble up the key event as WebView doesn't handle it 3352 return false; 3353 } 3354 3355 /** 3356 * @hide 3357 */ 3358 public void emulateShiftHeld() { 3359 mExtendSelection = false; 3360 mShiftIsPressed = true; 3361 nativeHideCursor(); 3362 } 3363 3364 private boolean commitCopy() { 3365 boolean copiedSomething = false; 3366 if (mExtendSelection) { 3367 // copy region so core operates on copy without touching orig. 3368 Region selection = new Region(nativeGetSelection()); 3369 if (selection.isEmpty() == false) { 3370 Toast.makeText(mContext 3371 , com.android.internal.R.string.text_copied 3372 , Toast.LENGTH_SHORT).show(); 3373 mWebViewCore.sendMessage(EventHub.GET_SELECTION, selection); 3374 copiedSomething = true; 3375 } 3376 mExtendSelection = false; 3377 } 3378 mShiftIsPressed = false; 3379 if (mTouchMode == TOUCH_SELECT_MODE) { 3380 mTouchMode = TOUCH_INIT_MODE; 3381 } 3382 return copiedSomething; 3383 } 3384 3385 // Set this as a hierarchy change listener so we can know when this view 3386 // is removed and still have access to our parent. 3387 @Override 3388 protected void onAttachedToWindow() { 3389 super.onAttachedToWindow(); 3390 ViewParent parent = getParent(); 3391 if (parent instanceof ViewGroup) { 3392 ViewGroup p = (ViewGroup) parent; 3393 p.setOnHierarchyChangeListener(this); 3394 } 3395 } 3396 3397 @Override 3398 protected void onDetachedFromWindow() { 3399 super.onDetachedFromWindow(); 3400 ViewParent parent = getParent(); 3401 if (parent instanceof ViewGroup) { 3402 ViewGroup p = (ViewGroup) parent; 3403 p.setOnHierarchyChangeListener(null); 3404 } 3405 3406 // Clean up the zoom controller 3407 mZoomButtonsController.setVisible(false); 3408 } 3409 3410 // Implementation for OnHierarchyChangeListener 3411 public void onChildViewAdded(View parent, View child) {} 3412 3413 public void onChildViewRemoved(View p, View child) { 3414 if (child == this) { 3415 clearTextEntry(); 3416 } 3417 } 3418 3419 /** 3420 * @deprecated WebView should not have implemented 3421 * ViewTreeObserver.OnGlobalFocusChangeListener. This method 3422 * does nothing now. 3423 */ 3424 @Deprecated 3425 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 3426 } 3427 3428 // To avoid drawing the cursor ring, and remove the TextView when our window 3429 // loses focus. 3430 @Override 3431 public void onWindowFocusChanged(boolean hasWindowFocus) { 3432 if (hasWindowFocus) { 3433 if (hasFocus()) { 3434 // If our window regained focus, and we have focus, then begin 3435 // drawing the cursor ring 3436 mDrawCursorRing = true; 3437 if (mNativeClass != 0) { 3438 nativeRecordButtons(true, false, true); 3439 if (inEditingMode()) { 3440 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0); 3441 } 3442 } 3443 } else { 3444 // If our window gained focus, but we do not have it, do not 3445 // draw the cursor ring. 3446 mDrawCursorRing = false; 3447 // We do not call nativeRecordButtons here because we assume 3448 // that when we lost focus, or window focus, it got called with 3449 // false for the first parameter 3450 } 3451 } else { 3452 if (getSettings().getBuiltInZoomControls() && !mZoomButtonsController.isVisible()) { 3453 /* 3454 * The zoom controls come in their own window, so our window 3455 * loses focus. Our policy is to not draw the cursor ring if 3456 * our window is not focused, but this is an exception since 3457 * the user can still navigate the web page with the zoom 3458 * controls showing. 3459 */ 3460 // If our window has lost focus, stop drawing the cursor ring 3461 mDrawCursorRing = false; 3462 } 3463 mGotKeyDown = false; 3464 mShiftIsPressed = false; 3465 if (mNativeClass != 0) { 3466 nativeRecordButtons(false, false, true); 3467 } 3468 setFocusControllerInactive(); 3469 } 3470 invalidate(); 3471 super.onWindowFocusChanged(hasWindowFocus); 3472 } 3473 3474 /* 3475 * Pass a message to WebCore Thread, telling the WebCore::Page's 3476 * FocusController to be "inactive" so that it will 3477 * not draw the blinking cursor. It gets set to "active" to draw the cursor 3478 * in WebViewCore.cpp, when the WebCore thread receives key events/clicks. 3479 */ 3480 /* package */ void setFocusControllerInactive() { 3481 // Do not need to also check whether mWebViewCore is null, because 3482 // mNativeClass is only set if mWebViewCore is non null 3483 if (mNativeClass == 0) return; 3484 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0); 3485 } 3486 3487 @Override 3488 protected void onFocusChanged(boolean focused, int direction, 3489 Rect previouslyFocusedRect) { 3490 if (DebugFlags.WEB_VIEW) { 3491 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); 3492 } 3493 if (focused) { 3494 // When we regain focus, if we have window focus, resume drawing 3495 // the cursor ring 3496 if (hasWindowFocus()) { 3497 mDrawCursorRing = true; 3498 if (mNativeClass != 0) { 3499 nativeRecordButtons(true, false, true); 3500 } 3501 //} else { 3502 // The WebView has gained focus while we do not have 3503 // windowfocus. When our window lost focus, we should have 3504 // called nativeRecordButtons(false...) 3505 } 3506 } else { 3507 // When we lost focus, unless focus went to the TextView (which is 3508 // true if we are in editing mode), stop drawing the cursor ring. 3509 if (!inEditingMode()) { 3510 mDrawCursorRing = false; 3511 if (mNativeClass != 0) { 3512 nativeRecordButtons(false, false, true); 3513 } 3514 setFocusControllerInactive(); 3515 } 3516 mGotKeyDown = false; 3517 } 3518 3519 super.onFocusChanged(focused, direction, previouslyFocusedRect); 3520 } 3521 3522 @Override 3523 protected void onSizeChanged(int w, int h, int ow, int oh) { 3524 super.onSizeChanged(w, h, ow, oh); 3525 // Center zooming to the center of the screen. 3526 if (mZoomScale == 0) { // unless we're already zooming 3527 mZoomCenterX = getViewWidth() * .5f; 3528 mZoomCenterY = getViewHeight() * .5f; 3529 } 3530 3531 // update mMinZoomScale if the minimum zoom scale is not fixed 3532 if (!mMinZoomScaleFixed) { 3533 mMinZoomScale = (float) getViewWidth() 3534 / (mDrawHistory ? mHistoryPicture.getWidth() 3535 : mZoomOverviewWidth); 3536 } 3537 3538 // we always force, in case our height changed, in which case we still 3539 // want to send the notification over to webkit 3540 setNewZoomScale(mActualScale, true); 3541 } 3542 3543 @Override 3544 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 3545 super.onScrollChanged(l, t, oldl, oldt); 3546 3547 sendOurVisibleRect(); 3548 } 3549 3550 3551 @Override 3552 public boolean dispatchKeyEvent(KeyEvent event) { 3553 boolean dispatch = true; 3554 3555 if (!inEditingMode()) { 3556 if (event.getAction() == KeyEvent.ACTION_DOWN) { 3557 mGotKeyDown = true; 3558 } else { 3559 if (!mGotKeyDown) { 3560 /* 3561 * We got a key up for which we were not the recipient of 3562 * the original key down. Don't give it to the view. 3563 */ 3564 dispatch = false; 3565 } 3566 mGotKeyDown = false; 3567 } 3568 } 3569 3570 if (dispatch) { 3571 return super.dispatchKeyEvent(event); 3572 } else { 3573 // We didn't dispatch, so let something else handle the key 3574 return false; 3575 } 3576 } 3577 3578 // Here are the snap align logic: 3579 // 1. If it starts nearly horizontally or vertically, snap align; 3580 // 2. If there is a dramitic direction change, let it go; 3581 // 3. If there is a same direction back and forth, lock it. 3582 3583 // adjustable parameters 3584 private int mMinLockSnapReverseDistance; 3585 private static final float MAX_SLOPE_FOR_DIAG = 1.5f; 3586 private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80; 3587 3588 @Override 3589 public boolean onTouchEvent(MotionEvent ev) { 3590 if (mNativeClass == 0 || !isClickable() || !isLongClickable()) { 3591 return false; 3592 } 3593 3594 if (DebugFlags.WEB_VIEW) { 3595 Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" 3596 + mTouchMode); 3597 } 3598 3599 int action = ev.getAction(); 3600 float x = ev.getX(); 3601 float y = ev.getY(); 3602 long eventTime = ev.getEventTime(); 3603 3604 // Due to the touch screen edge effect, a touch closer to the edge 3605 // always snapped to the edge. As getViewWidth() can be different from 3606 // getWidth() due to the scrollbar, adjusting the point to match 3607 // getViewWidth(). Same applied to the height. 3608 if (x > getViewWidth() - 1) { 3609 x = getViewWidth() - 1; 3610 } 3611 if (y > getViewHeight() - 1) { 3612 y = getViewHeight() - 1; 3613 } 3614 3615 // pass the touch events from UI thread to WebCore thread 3616 if (mForwardTouchEvents && (action != MotionEvent.ACTION_MOVE 3617 || eventTime - mLastSentTouchTime > TOUCH_SENT_INTERVAL)) { 3618 WebViewCore.TouchEventData ted = new WebViewCore.TouchEventData(); 3619 ted.mAction = action; 3620 ted.mX = viewToContentX((int) x + mScrollX); 3621 ted.mY = viewToContentY((int) y + mScrollY); 3622 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 3623 mLastSentTouchTime = eventTime; 3624 } 3625 3626 int deltaX = (int) (mLastTouchX - x); 3627 int deltaY = (int) (mLastTouchY - y); 3628 3629 switch (action) { 3630 case MotionEvent.ACTION_DOWN: { 3631 if (!mScroller.isFinished()) { 3632 // stop the current scroll animation, but if this is 3633 // the start of a fling, allow it to add to the current 3634 // fling's velocity 3635 mScroller.abortAnimation(); 3636 mTouchMode = TOUCH_DRAG_START_MODE; 3637 mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE); 3638 } else if (mShiftIsPressed) { 3639 mSelectX = mScrollX + (int) x; 3640 mSelectY = mScrollY + (int) y; 3641 mTouchMode = TOUCH_SELECT_MODE; 3642 if (DebugFlags.WEB_VIEW) { 3643 Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY); 3644 } 3645 nativeMoveSelection(viewToContentX(mSelectX), 3646 viewToContentY(mSelectY), false); 3647 mTouchSelection = mExtendSelection = true; 3648 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { 3649 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); 3650 if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { 3651 mTouchMode = TOUCH_DOUBLE_TAP_MODE; 3652 } else { 3653 // commit the short press action for the previous tap 3654 doShortPress(); 3655 // continue, mTouchMode should be still TOUCH_INIT_MODE 3656 } 3657 } else { 3658 mTouchMode = TOUCH_INIT_MODE; 3659 mPreventDrag = mForwardTouchEvents; 3660 mWebViewCore.sendMessage( 3661 EventHub.UPDATE_FRAME_CACHE_IF_LOADING); 3662 if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { 3663 EventLog.writeEvent(EVENT_LOG_DOUBLE_TAP_DURATION, 3664 (eventTime - mLastTouchUpTime), eventTime); 3665 } 3666 } 3667 // Trigger the link 3668 if (mTouchMode == TOUCH_INIT_MODE 3669 || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 3670 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3671 .obtainMessage(SWITCH_TO_SHORTPRESS), TAP_TIMEOUT); 3672 } 3673 // Remember where the motion event started 3674 mLastTouchX = x; 3675 mLastTouchY = y; 3676 mLastTouchTime = eventTime; 3677 mVelocityTracker = VelocityTracker.obtain(); 3678 mSnapScrollMode = SNAP_NONE; 3679 break; 3680 } 3681 case MotionEvent.ACTION_MOVE: { 3682 if (mTouchMode == TOUCH_DONE_MODE) { 3683 // no dragging during scroll zoom animation 3684 break; 3685 } 3686 mVelocityTracker.addMovement(ev); 3687 3688 if (mTouchMode != TOUCH_DRAG_MODE) { 3689 if (mTouchMode == TOUCH_SELECT_MODE) { 3690 mSelectX = mScrollX + (int) x; 3691 mSelectY = mScrollY + (int) y; 3692 if (DebugFlags.WEB_VIEW) { 3693 Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY); 3694 } 3695 nativeMoveSelection(viewToContentX(mSelectX), 3696 viewToContentY(mSelectY), true); 3697 invalidate(); 3698 break; 3699 } 3700 if (mPreventDrag || (deltaX * deltaX + deltaY * deltaY) 3701 < mTouchSlopSquare) { 3702 break; 3703 } 3704 3705 if (mTouchMode == TOUCH_SHORTPRESS_MODE 3706 || mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 3707 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3708 } else if (mTouchMode == TOUCH_INIT_MODE 3709 || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 3710 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3711 } 3712 3713 // if it starts nearly horizontal or vertical, enforce it 3714 int ax = Math.abs(deltaX); 3715 int ay = Math.abs(deltaY); 3716 if (ax > MAX_SLOPE_FOR_DIAG * ay) { 3717 mSnapScrollMode = SNAP_X; 3718 mSnapPositive = deltaX > 0; 3719 } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { 3720 mSnapScrollMode = SNAP_Y; 3721 mSnapPositive = deltaY > 0; 3722 } 3723 3724 mTouchMode = TOUCH_DRAG_MODE; 3725 WebViewCore.pauseUpdate(mWebViewCore); 3726 if (!mDragFromTextInput) { 3727 nativeHideCursor(); 3728 } 3729 WebSettings settings = getSettings(); 3730 if (settings.supportZoom() 3731 && settings.getBuiltInZoomControls() 3732 && !mZoomButtonsController.isVisible() 3733 && mMinZoomScale < mMaxZoomScale) { 3734 mZoomButtonsController.setVisible(true); 3735 } 3736 } 3737 3738 // do pan 3739 int newScrollX = pinLocX(mScrollX + deltaX); 3740 deltaX = newScrollX - mScrollX; 3741 int newScrollY = pinLocY(mScrollY + deltaY); 3742 deltaY = newScrollY - mScrollY; 3743 boolean done = false; 3744 if (deltaX == 0 && deltaY == 0) { 3745 done = true; 3746 } else { 3747 if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) { 3748 int ax = Math.abs(deltaX); 3749 int ay = Math.abs(deltaY); 3750 if (mSnapScrollMode == SNAP_X) { 3751 // radical change means getting out of snap mode 3752 if (ay > MAX_SLOPE_FOR_DIAG * ax 3753 && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) { 3754 mSnapScrollMode = SNAP_NONE; 3755 } 3756 // reverse direction means lock in the snap mode 3757 if ((ax > MAX_SLOPE_FOR_DIAG * ay) && 3758 ((mSnapPositive && 3759 deltaX < -mMinLockSnapReverseDistance) 3760 || (!mSnapPositive && 3761 deltaX > mMinLockSnapReverseDistance))) { 3762 mSnapScrollMode = SNAP_X_LOCK; 3763 } 3764 } else { 3765 // radical change means getting out of snap mode 3766 if ((ax > MAX_SLOPE_FOR_DIAG * ay) 3767 && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) { 3768 mSnapScrollMode = SNAP_NONE; 3769 } 3770 // reverse direction means lock in the snap mode 3771 if ((ay > MAX_SLOPE_FOR_DIAG * ax) && 3772 ((mSnapPositive && 3773 deltaY < -mMinLockSnapReverseDistance) 3774 || (!mSnapPositive && 3775 deltaY > mMinLockSnapReverseDistance))) { 3776 mSnapScrollMode = SNAP_Y_LOCK; 3777 } 3778 } 3779 } 3780 3781 if (mSnapScrollMode == SNAP_X 3782 || mSnapScrollMode == SNAP_X_LOCK) { 3783 scrollBy(deltaX, 0); 3784 mLastTouchX = x; 3785 } else if (mSnapScrollMode == SNAP_Y 3786 || mSnapScrollMode == SNAP_Y_LOCK) { 3787 scrollBy(0, deltaY); 3788 mLastTouchY = y; 3789 } else { 3790 scrollBy(deltaX, deltaY); 3791 mLastTouchX = x; 3792 mLastTouchY = y; 3793 } 3794 mLastTouchTime = eventTime; 3795 mUserScroll = true; 3796 } 3797 3798 if (!getSettings().getBuiltInZoomControls()) { 3799 boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; 3800 if (mZoomControls != null && showPlusMinus) { 3801 if (mZoomControls.getVisibility() == View.VISIBLE) { 3802 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 3803 } else { 3804 mZoomControls.show(showPlusMinus, false); 3805 } 3806 mPrivateHandler.postDelayed(mZoomControlRunnable, 3807 ZOOM_CONTROLS_TIMEOUT); 3808 } 3809 } 3810 3811 if (done) { 3812 // return false to indicate that we can't pan out of the 3813 // view space 3814 return false; 3815 } 3816 break; 3817 } 3818 case MotionEvent.ACTION_UP: { 3819 mLastTouchUpTime = eventTime; 3820 switch (mTouchMode) { 3821 case TOUCH_DOUBLE_TAP_MODE: // double tap 3822 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3823 mTouchMode = TOUCH_DONE_MODE; 3824 doDoubleTap(); 3825 break; 3826 case TOUCH_INIT_MODE: // tap 3827 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3828 if (!mPreventDrag) { 3829 mPrivateHandler.sendMessageDelayed( 3830 mPrivateHandler.obtainMessage( 3831 RELEASE_SINGLE_TAP), 3832 ViewConfiguration.getDoubleTapTimeout()); 3833 } 3834 break; 3835 case TOUCH_SHORTPRESS_START_MODE: 3836 case TOUCH_SHORTPRESS_MODE: 3837 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3838 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3839 mTouchMode = TOUCH_DONE_MODE; 3840 doShortPress(); 3841 break; 3842 case TOUCH_SELECT_MODE: 3843 commitCopy(); 3844 mTouchSelection = false; 3845 break; 3846 case TOUCH_DRAG_MODE: 3847 // redraw in high-quality, as we're done dragging 3848 invalidate(); 3849 // if the user waits a while w/o moving before the 3850 // up, we don't want to do a fling 3851 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { 3852 mVelocityTracker.addMovement(ev); 3853 doFling(); 3854 break; 3855 } 3856 mLastVelocity = 0; 3857 WebViewCore.resumeUpdate(mWebViewCore); 3858 break; 3859 case TOUCH_DRAG_START_MODE: 3860 case TOUCH_DONE_MODE: 3861 // do nothing 3862 break; 3863 } 3864 // we also use mVelocityTracker == null to tell us that we are 3865 // not "moving around", so we can take the slower/prettier 3866 // mode in the drawing code 3867 if (mVelocityTracker != null) { 3868 mVelocityTracker.recycle(); 3869 mVelocityTracker = null; 3870 } 3871 break; 3872 } 3873 case MotionEvent.ACTION_CANCEL: { 3874 // we also use mVelocityTracker == null to tell us that we are 3875 // not "moving around", so we can take the slower/prettier 3876 // mode in the drawing code 3877 if (mVelocityTracker != null) { 3878 mVelocityTracker.recycle(); 3879 mVelocityTracker = null; 3880 } 3881 if (mTouchMode == TOUCH_DRAG_MODE) { 3882 WebViewCore.resumeUpdate(mWebViewCore); 3883 } 3884 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 3885 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3886 mTouchMode = TOUCH_DONE_MODE; 3887 nativeHideCursor(); 3888 break; 3889 } 3890 } 3891 return true; 3892 } 3893 3894 private long mTrackballFirstTime = 0; 3895 private long mTrackballLastTime = 0; 3896 private float mTrackballRemainsX = 0.0f; 3897 private float mTrackballRemainsY = 0.0f; 3898 private int mTrackballXMove = 0; 3899 private int mTrackballYMove = 0; 3900 private boolean mExtendSelection = false; 3901 private boolean mTouchSelection = false; 3902 private static final int TRACKBALL_KEY_TIMEOUT = 1000; 3903 private static final int TRACKBALL_TIMEOUT = 200; 3904 private static final int TRACKBALL_WAIT = 100; 3905 private static final int TRACKBALL_SCALE = 400; 3906 private static final int TRACKBALL_SCROLL_COUNT = 5; 3907 private static final int TRACKBALL_MOVE_COUNT = 10; 3908 private static final int TRACKBALL_MULTIPLIER = 3; 3909 private static final int SELECT_CURSOR_OFFSET = 16; 3910 private int mSelectX = 0; 3911 private int mSelectY = 0; 3912 private boolean mShiftIsPressed = false; 3913 private boolean mTrackballDown = false; 3914 private long mTrackballUpTime = 0; 3915 private long mLastCursorTime = 0; 3916 private Rect mLastCursorBounds; 3917 3918 // Set by default; BrowserActivity clears to interpret trackball data 3919 // directly for movement. Currently, the framework only passes 3920 // arrow key events, not trackball events, from one child to the next 3921 private boolean mMapTrackballToArrowKeys = true; 3922 3923 public void setMapTrackballToArrowKeys(boolean setMap) { 3924 mMapTrackballToArrowKeys = setMap; 3925 } 3926 3927 void resetTrackballTime() { 3928 mTrackballLastTime = 0; 3929 } 3930 3931 @Override 3932 public boolean onTrackballEvent(MotionEvent ev) { 3933 long time = ev.getEventTime(); 3934 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) { 3935 if (ev.getY() > 0) pageDown(true); 3936 if (ev.getY() < 0) pageUp(true); 3937 return true; 3938 } 3939 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 3940 mTrackballDown = true; 3941 if (mNativeClass == 0) { 3942 return false; 3943 } 3944 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 3945 if (time - mLastCursorTime <= TRACKBALL_TIMEOUT 3946 && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { 3947 nativeSelectBestAt(mLastCursorBounds); 3948 } 3949 if (DebugFlags.WEB_VIEW) { 3950 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev 3951 + " time=" + time 3952 + " mLastCursorTime=" + mLastCursorTime); 3953 } 3954 if (isInTouchMode()) requestFocusFromTouch(); 3955 return false; // let common code in onKeyDown at it 3956 } 3957 if (ev.getAction() == MotionEvent.ACTION_UP) { 3958 // LONG_PRESS_CENTER is set in common onKeyDown 3959 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 3960 mTrackballDown = false; 3961 mTrackballUpTime = time; 3962 if (mShiftIsPressed) { 3963 if (mExtendSelection) { 3964 commitCopy(); 3965 } else { 3966 mExtendSelection = true; 3967 } 3968 } 3969 if (DebugFlags.WEB_VIEW) { 3970 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev 3971 + " time=" + time 3972 ); 3973 } 3974 return false; // let common code in onKeyUp at it 3975 } 3976 if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { 3977 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); 3978 return false; 3979 } 3980 if (mTrackballDown) { 3981 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit"); 3982 return true; // discard move if trackball is down 3983 } 3984 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { 3985 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); 3986 return true; 3987 } 3988 // TODO: alternatively we can do panning as touch does 3989 switchOutDrawHistory(); 3990 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { 3991 if (DebugFlags.WEB_VIEW) { 3992 Log.v(LOGTAG, "onTrackballEvent time=" 3993 + time + " last=" + mTrackballLastTime); 3994 } 3995 mTrackballFirstTime = time; 3996 mTrackballXMove = mTrackballYMove = 0; 3997 } 3998 mTrackballLastTime = time; 3999 if (DebugFlags.WEB_VIEW) { 4000 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); 4001 } 4002 mTrackballRemainsX += ev.getX(); 4003 mTrackballRemainsY += ev.getY(); 4004 doTrackball(time); 4005 return true; 4006 } 4007 4008 void moveSelection(float xRate, float yRate) { 4009 if (mNativeClass == 0) 4010 return; 4011 int width = getViewWidth(); 4012 int height = getViewHeight(); 4013 mSelectX += scaleTrackballX(xRate, width); 4014 mSelectY += scaleTrackballY(yRate, height); 4015 int maxX = width + mScrollX; 4016 int maxY = height + mScrollY; 4017 mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET 4018 , mSelectX)); 4019 mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET 4020 , mSelectY)); 4021 if (DebugFlags.WEB_VIEW) { 4022 Log.v(LOGTAG, "moveSelection" 4023 + " mSelectX=" + mSelectX 4024 + " mSelectY=" + mSelectY 4025 + " mScrollX=" + mScrollX 4026 + " mScrollY=" + mScrollY 4027 + " xRate=" + xRate 4028 + " yRate=" + yRate 4029 ); 4030 } 4031 nativeMoveSelection(viewToContentX(mSelectX), 4032 viewToContentY(mSelectY), mExtendSelection); 4033 int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET 4034 : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 4035 : 0; 4036 int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET 4037 : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 4038 : 0; 4039 pinScrollBy(scrollX, scrollY, true, 0); 4040 Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); 4041 requestRectangleOnScreen(select); 4042 invalidate(); 4043 } 4044 4045 private int scaleTrackballX(float xRate, int width) { 4046 int xMove = (int) (xRate / TRACKBALL_SCALE * width); 4047 int nextXMove = xMove; 4048 if (xMove > 0) { 4049 if (xMove > mTrackballXMove) { 4050 xMove -= mTrackballXMove; 4051 } 4052 } else if (xMove < mTrackballXMove) { 4053 xMove -= mTrackballXMove; 4054 } 4055 mTrackballXMove = nextXMove; 4056 return xMove; 4057 } 4058 4059 private int scaleTrackballY(float yRate, int height) { 4060 int yMove = (int) (yRate / TRACKBALL_SCALE * height); 4061 int nextYMove = yMove; 4062 if (yMove > 0) { 4063 if (yMove > mTrackballYMove) { 4064 yMove -= mTrackballYMove; 4065 } 4066 } else if (yMove < mTrackballYMove) { 4067 yMove -= mTrackballYMove; 4068 } 4069 mTrackballYMove = nextYMove; 4070 return yMove; 4071 } 4072 4073 private int keyCodeToSoundsEffect(int keyCode) { 4074 switch(keyCode) { 4075 case KeyEvent.KEYCODE_DPAD_UP: 4076 return SoundEffectConstants.NAVIGATION_UP; 4077 case KeyEvent.KEYCODE_DPAD_RIGHT: 4078 return SoundEffectConstants.NAVIGATION_RIGHT; 4079 case KeyEvent.KEYCODE_DPAD_DOWN: 4080 return SoundEffectConstants.NAVIGATION_DOWN; 4081 case KeyEvent.KEYCODE_DPAD_LEFT: 4082 return SoundEffectConstants.NAVIGATION_LEFT; 4083 } 4084 throw new IllegalArgumentException("keyCode must be one of " + 4085 "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + 4086 "KEYCODE_DPAD_LEFT}."); 4087 } 4088 4089 private void doTrackball(long time) { 4090 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime); 4091 if (elapsed == 0) { 4092 elapsed = TRACKBALL_TIMEOUT; 4093 } 4094 float xRate = mTrackballRemainsX * 1000 / elapsed; 4095 float yRate = mTrackballRemainsY * 1000 / elapsed; 4096 if (mShiftIsPressed) { 4097 moveSelection(xRate, yRate); 4098 mTrackballRemainsX = mTrackballRemainsY = 0; 4099 return; 4100 } 4101 float ax = Math.abs(xRate); 4102 float ay = Math.abs(yRate); 4103 float maxA = Math.max(ax, ay); 4104 if (DebugFlags.WEB_VIEW) { 4105 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed 4106 + " xRate=" + xRate 4107 + " yRate=" + yRate 4108 + " mTrackballRemainsX=" + mTrackballRemainsX 4109 + " mTrackballRemainsY=" + mTrackballRemainsY); 4110 } 4111 int width = mContentWidth - getViewWidth(); 4112 int height = mContentHeight - getViewHeight(); 4113 if (width < 0) width = 0; 4114 if (height < 0) height = 0; 4115 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); 4116 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); 4117 maxA = Math.max(ax, ay); 4118 int count = Math.max(0, (int) maxA); 4119 int oldScrollX = mScrollX; 4120 int oldScrollY = mScrollY; 4121 if (count > 0) { 4122 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? 4123 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : 4124 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : 4125 KeyEvent.KEYCODE_DPAD_RIGHT; 4126 count = Math.min(count, TRACKBALL_MOVE_COUNT); 4127 if (DebugFlags.WEB_VIEW) { 4128 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode 4129 + " count=" + count 4130 + " mTrackballRemainsX=" + mTrackballRemainsX 4131 + " mTrackballRemainsY=" + mTrackballRemainsY); 4132 } 4133 if (navHandledKey(selectKeyCode, count, false, time, false)) { 4134 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); 4135 } 4136 mTrackballRemainsX = mTrackballRemainsY = 0; 4137 } 4138 if (count >= TRACKBALL_SCROLL_COUNT) { 4139 int xMove = scaleTrackballX(xRate, width); 4140 int yMove = scaleTrackballY(yRate, height); 4141 if (DebugFlags.WEB_VIEW) { 4142 Log.v(LOGTAG, "doTrackball pinScrollBy" 4143 + " count=" + count 4144 + " xMove=" + xMove + " yMove=" + yMove 4145 + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) 4146 + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) 4147 ); 4148 } 4149 if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { 4150 xMove = 0; 4151 } 4152 if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) { 4153 yMove = 0; 4154 } 4155 if (xMove != 0 || yMove != 0) { 4156 pinScrollBy(xMove, yMove, true, 0); 4157 } 4158 mUserScroll = true; 4159 } 4160 } 4161 4162 private int computeMaxScrollY() { 4163 int maxContentH = computeVerticalScrollRange() + getTitleHeight(); 4164 return Math.max(maxContentH - getHeight(), getTitleHeight()); 4165 } 4166 4167 public void flingScroll(int vx, int vy) { 4168 int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); 4169 int maxY = computeMaxScrollY(); 4170 4171 mScroller.fling(mScrollX, mScrollY, vx, vy, 0, maxX, 0, maxY); 4172 invalidate(); 4173 } 4174 4175 private void doFling() { 4176 if (mVelocityTracker == null) { 4177 return; 4178 } 4179 int maxX = Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); 4180 int maxY = computeMaxScrollY(); 4181 4182 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling); 4183 int vx = (int) mVelocityTracker.getXVelocity(); 4184 int vy = (int) mVelocityTracker.getYVelocity(); 4185 4186 if (mSnapScrollMode != SNAP_NONE) { 4187 if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_X_LOCK) { 4188 vy = 0; 4189 } else { 4190 vx = 0; 4191 } 4192 } 4193 4194 if (true /* EMG release: make our fling more like Maps' */) { 4195 // maps cuts their velocity in half 4196 vx = vx * 3 / 4; 4197 vy = vy * 3 / 4; 4198 } 4199 if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { 4200 WebViewCore.resumeUpdate(mWebViewCore); 4201 return; 4202 } 4203 float currentVelocity = mScroller.getCurrVelocity(); 4204 if (mLastVelocity > 0 && currentVelocity > 0) { 4205 float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX) 4206 - Math.atan2(vy, vx))); 4207 final float circle = (float) (Math.PI) * 2.0f; 4208 if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) { 4209 vx += currentVelocity * mLastVelX / mLastVelocity; 4210 vy += currentVelocity * mLastVelY / mLastVelocity; 4211 if (DebugFlags.WEB_VIEW) { 4212 Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy); 4213 } 4214 } else if (DebugFlags.WEB_VIEW) { 4215 Log.v(LOGTAG, "doFling missed " + deltaR / circle); 4216 } 4217 } else if (DebugFlags.WEB_VIEW) { 4218 Log.v(LOGTAG, "doFling start last=" + mLastVelocity 4219 + " current=" + currentVelocity 4220 + " vx=" + vx + " vy=" + vy 4221 + " maxX=" + maxX + " maxY=" + maxY 4222 + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY); 4223 } 4224 mLastVelX = vx; 4225 mLastVelY = vy; 4226 mLastVelocity = (float) Math.hypot(vx, vy); 4227 4228 mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY); 4229 // TODO: duration is calculated based on velocity, if the range is 4230 // small, the animation will stop before duration is up. We may 4231 // want to calculate how long the animation is going to run to precisely 4232 // resume the webcore update. 4233 final int time = mScroller.getDuration(); 4234 mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_UPDATE, time); 4235 invalidate(); 4236 } 4237 4238 private boolean zoomWithPreview(float scale) { 4239 float oldScale = mActualScale; 4240 mInitialScrollX = mScrollX; 4241 mInitialScrollY = mScrollY; 4242 4243 // snap to DEFAULT_SCALE if it is close 4244 if (scale > (mDefaultScale - 0.05) && scale < (mDefaultScale + 0.05)) { 4245 scale = mDefaultScale; 4246 } 4247 4248 setNewZoomScale(scale, false); 4249 4250 if (oldScale != mActualScale) { 4251 // use mZoomPickerScale to see zoom preview first 4252 mZoomStart = SystemClock.uptimeMillis(); 4253 mInvInitialZoomScale = 1.0f / oldScale; 4254 mInvFinalZoomScale = 1.0f / mActualScale; 4255 mZoomScale = mActualScale; 4256 if (!mInZoomOverview) { 4257 mLastScale = scale; 4258 } 4259 invalidate(); 4260 return true; 4261 } else { 4262 return false; 4263 } 4264 } 4265 4266 /** 4267 * Returns a view containing zoom controls i.e. +/- buttons. The caller is 4268 * in charge of installing this view to the view hierarchy. This view will 4269 * become visible when the user starts scrolling via touch and fade away if 4270 * the user does not interact with it. 4271 * <p/> 4272 * API version 3 introduces a built-in zoom mechanism that is shown 4273 * automatically by the MapView. This is the preferred approach for 4274 * showing the zoom UI. 4275 * 4276 * @deprecated The built-in zoom mechanism is preferred, see 4277 * {@link WebSettings#setBuiltInZoomControls(boolean)}. 4278 */ 4279 @Deprecated 4280 public View getZoomControls() { 4281 if (!getSettings().supportZoom()) { 4282 Log.w(LOGTAG, "This WebView doesn't support zoom."); 4283 return null; 4284 } 4285 if (mZoomControls == null) { 4286 mZoomControls = createZoomControls(); 4287 4288 /* 4289 * need to be set to VISIBLE first so that getMeasuredHeight() in 4290 * {@link #onSizeChanged()} can return the measured value for proper 4291 * layout. 4292 */ 4293 mZoomControls.setVisibility(View.VISIBLE); 4294 mZoomControlRunnable = new Runnable() { 4295 public void run() { 4296 4297 /* Don't dismiss the controls if the user has 4298 * focus on them. Wait and check again later. 4299 */ 4300 if (!mZoomControls.hasFocus()) { 4301 mZoomControls.hide(); 4302 } else { 4303 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4304 mPrivateHandler.postDelayed(mZoomControlRunnable, 4305 ZOOM_CONTROLS_TIMEOUT); 4306 } 4307 } 4308 }; 4309 } 4310 return mZoomControls; 4311 } 4312 4313 private ExtendedZoomControls createZoomControls() { 4314 ExtendedZoomControls zoomControls = new ExtendedZoomControls(mContext 4315 , null); 4316 zoomControls.setOnZoomInClickListener(new OnClickListener() { 4317 public void onClick(View v) { 4318 // reset time out 4319 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4320 mPrivateHandler.postDelayed(mZoomControlRunnable, 4321 ZOOM_CONTROLS_TIMEOUT); 4322 zoomIn(); 4323 } 4324 }); 4325 zoomControls.setOnZoomOutClickListener(new OnClickListener() { 4326 public void onClick(View v) { 4327 // reset time out 4328 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4329 mPrivateHandler.postDelayed(mZoomControlRunnable, 4330 ZOOM_CONTROLS_TIMEOUT); 4331 zoomOut(); 4332 } 4333 }); 4334 return zoomControls; 4335 } 4336 4337 /** 4338 * Gets the {@link ZoomButtonsController} which can be used to add 4339 * additional buttons to the zoom controls window. 4340 * 4341 * @return The instance of {@link ZoomButtonsController} used by this class, 4342 * or null if it is unavailable. 4343 * @hide 4344 */ 4345 public ZoomButtonsController getZoomButtonsController() { 4346 return mZoomButtonsController; 4347 } 4348 4349 /** 4350 * Perform zoom in in the webview 4351 * @return TRUE if zoom in succeeds. FALSE if no zoom changes. 4352 */ 4353 public boolean zoomIn() { 4354 // TODO: alternatively we can disallow this during draw history mode 4355 switchOutDrawHistory(); 4356 // Center zooming to the center of the screen. 4357 if (mInZoomOverview) { 4358 // if in overview mode, bring it back to normal mode 4359 mLastTouchX = getViewWidth() * .5f; 4360 mLastTouchY = getViewHeight() * .5f; 4361 doDoubleTap(); 4362 return true; 4363 } else { 4364 mZoomCenterX = getViewWidth() * .5f; 4365 mZoomCenterY = getViewHeight() * .5f; 4366 return zoomWithPreview(mActualScale * 1.25f); 4367 } 4368 } 4369 4370 /** 4371 * Perform zoom out in the webview 4372 * @return TRUE if zoom out succeeds. FALSE if no zoom changes. 4373 */ 4374 public boolean zoomOut() { 4375 // TODO: alternatively we can disallow this during draw history mode 4376 switchOutDrawHistory(); 4377 float scale = mActualScale * 0.8f; 4378 if (scale < (mMinZoomScale + 0.1f) 4379 && mWebViewCore.getSettings().getUseWideViewPort()) { 4380 // when zoom out to min scale, switch to overview mode 4381 doDoubleTap(); 4382 return true; 4383 } else { 4384 // Center zooming to the center of the screen. 4385 mZoomCenterX = getViewWidth() * .5f; 4386 mZoomCenterY = getViewHeight() * .5f; 4387 return zoomWithPreview(scale); 4388 } 4389 } 4390 4391 private void updateSelection() { 4392 if (mNativeClass == 0) { 4393 return; 4394 } 4395 // mLastTouchX and mLastTouchY are the point in the current viewport 4396 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 4397 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 4398 Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop, 4399 contentX + mNavSlop, contentY + mNavSlop); 4400 nativeSelectBestAt(rect); 4401 } 4402 4403 /** 4404 * Scroll the focused text field/area to match the WebTextView 4405 * @param x New x position of the WebTextView in view coordinates 4406 * @param y New y position of the WebTextView in view coordinates 4407 */ 4408 /*package*/ void scrollFocusedTextInput(int x, int y) { 4409 if (!inEditingMode() || mWebViewCore == null) { 4410 return; 4411 } 4412 mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, viewToContentX(x), 4413 viewToContentY(y)); 4414 } 4415 4416 /** 4417 * Set our starting point and time for a drag from the WebTextView. 4418 */ 4419 /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) { 4420 if (!inEditingMode()) { 4421 return; 4422 } 4423 mLastTouchX = x + (float) (mWebTextView.getLeft() - mScrollX); 4424 mLastTouchY = y + (float) (mWebTextView.getTop() - mScrollY); 4425 mLastTouchTime = eventTime; 4426 if (!mScroller.isFinished()) { 4427 abortAnimation(); 4428 mPrivateHandler.removeMessages(RESUME_WEBCORE_UPDATE); 4429 } 4430 mSnapScrollMode = SNAP_NONE; 4431 mVelocityTracker = VelocityTracker.obtain(); 4432 mTouchMode = TOUCH_DRAG_START_MODE; 4433 } 4434 4435 /** 4436 * Given a motion event from the WebTextView, set its location to our 4437 * coordinates, and handle the event. 4438 */ 4439 /*package*/ boolean textFieldDrag(MotionEvent event) { 4440 if (!inEditingMode()) { 4441 return false; 4442 } 4443 mDragFromTextInput = true; 4444 event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX), 4445 (float) (mWebTextView.getTop() - mScrollY)); 4446 boolean result = onTouchEvent(event); 4447 mDragFromTextInput = false; 4448 return result; 4449 } 4450 4451 /** 4452 * Do a touch up from a WebTextView. This will be handled by webkit to 4453 * change the selection. 4454 * @param event MotionEvent in the WebTextView's coordinates. 4455 */ 4456 /*package*/ void touchUpOnTextField(MotionEvent event) { 4457 if (!inEditingMode()) { 4458 return; 4459 } 4460 int x = viewToContentX((int) event.getX() + mWebTextView.getLeft()); 4461 int y = viewToContentY((int) event.getY() + mWebTextView.getTop()); 4462 // In case the soft keyboard has been dismissed, bring it back up. 4463 InputMethodManager.getInstance(getContext()).showSoftInput(mWebTextView, 4464 0); 4465 if (nativeFocusNodePointer() != nativeCursorNodePointer()) { 4466 nativeMotionUp(x, y, mNavSlop); 4467 } 4468 nativeTextInputMotionUp(x, y); 4469 } 4470 4471 /*package*/ void shortPressOnTextField() { 4472 if (inEditingMode()) { 4473 View v = mWebTextView; 4474 int x = viewToContentX((v.getLeft() + v.getRight()) >> 1); 4475 int y = viewToContentY((v.getTop() + v.getBottom()) >> 1); 4476 nativeTextInputMotionUp(x, y); 4477 } 4478 } 4479 4480 private void doShortPress() { 4481 if (mNativeClass == 0) { 4482 return; 4483 } 4484 switchOutDrawHistory(); 4485 // mLastTouchX and mLastTouchY are the point in the current viewport 4486 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 4487 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 4488 if (nativeMotionUp(contentX, contentY, mNavSlop)) { 4489 if (mLogEvent) { 4490 Checkin.updateStats(mContext.getContentResolver(), 4491 Checkin.Stats.Tag.BROWSER_SNAP_CENTER, 1, 0.0); 4492 } 4493 } 4494 if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { 4495 playSoundEffect(SoundEffectConstants.CLICK); 4496 } 4497 } 4498 4499 private void doDoubleTap() { 4500 if (mWebViewCore.getSettings().getUseWideViewPort() == false) { 4501 return; 4502 } 4503 mZoomCenterX = mLastTouchX; 4504 mZoomCenterY = mLastTouchY; 4505 mInZoomOverview = !mInZoomOverview; 4506 // remove the zoom control after double tap 4507 if (getSettings().getBuiltInZoomControls()) { 4508 if (mZoomButtonsController.isVisible()) { 4509 mZoomButtonsController.setVisible(false); 4510 } 4511 } else { 4512 if (mZoomControlRunnable != null) { 4513 mPrivateHandler.removeCallbacks(mZoomControlRunnable); 4514 } 4515 if (mZoomControls != null) { 4516 mZoomControls.hide(); 4517 } 4518 } 4519 if (mInZoomOverview) { 4520 zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth); 4521 } else { 4522 // mLastTouchX and mLastTouchY are the point in the current viewport 4523 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 4524 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 4525 int left = nativeGetBlockLeftEdge(contentX, contentY, mActualScale); 4526 if (left != NO_LEFTEDGE) { 4527 // add a 5pt padding to the left edge. Re-calculate the zoom 4528 // center so that the new scroll x will be on the left edge. 4529 mZoomCenterX = left < 5 ? 0 : (left - 5) * mLastScale 4530 * mActualScale / (mLastScale - mActualScale); 4531 } 4532 zoomWithPreview(mLastScale); 4533 } 4534 } 4535 4536 // Called by JNI to handle a touch on a node representing an email address, 4537 // address, or phone number 4538 private void overrideLoading(String url) { 4539 mCallbackProxy.uiOverrideUrlLoading(url); 4540 } 4541 4542 // called by JNI 4543 private void sendPluginState(int state) { 4544 WebViewCore.PluginStateData psd = new WebViewCore.PluginStateData(); 4545 psd.mFrame = nativeCursorFramePointer(); 4546 psd.mNode = nativeCursorNodePointer(); 4547 psd.mState = state; 4548 mWebViewCore.sendMessage(EventHub.PLUGIN_STATE, psd); 4549 } 4550 4551 @Override 4552 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 4553 boolean result = false; 4554 if (inEditingMode()) { 4555 result = mWebTextView.requestFocus(direction, 4556 previouslyFocusedRect); 4557 } else { 4558 result = super.requestFocus(direction, previouslyFocusedRect); 4559 if (mWebViewCore.getSettings().getNeedInitialFocus()) { 4560 // For cases such as GMail, where we gain focus from a direction, 4561 // we want to move to the first available link. 4562 // FIXME: If there are no visible links, we may not want to 4563 int fakeKeyDirection = 0; 4564 switch(direction) { 4565 case View.FOCUS_UP: 4566 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP; 4567 break; 4568 case View.FOCUS_DOWN: 4569 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN; 4570 break; 4571 case View.FOCUS_LEFT: 4572 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT; 4573 break; 4574 case View.FOCUS_RIGHT: 4575 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT; 4576 break; 4577 default: 4578 return result; 4579 } 4580 if (mNativeClass != 0 && !nativeHasCursorNode()) { 4581 navHandledKey(fakeKeyDirection, 1, true, 0, true); 4582 } 4583 } 4584 } 4585 return result; 4586 } 4587 4588 @Override 4589 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 4590 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 4591 4592 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 4593 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 4594 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 4595 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 4596 4597 int measuredHeight = heightSize; 4598 int measuredWidth = widthSize; 4599 4600 // Grab the content size from WebViewCore. 4601 int contentHeight = contentToViewDimension(mContentHeight); 4602 int contentWidth = contentToViewDimension(mContentWidth); 4603 4604// Log.d(LOGTAG, "------- measure " + heightMode); 4605 4606 if (heightMode != MeasureSpec.EXACTLY) { 4607 mHeightCanMeasure = true; 4608 measuredHeight = contentHeight; 4609 if (heightMode == MeasureSpec.AT_MOST) { 4610 // If we are larger than the AT_MOST height, then our height can 4611 // no longer be measured and we should scroll internally. 4612 if (measuredHeight > heightSize) { 4613 measuredHeight = heightSize; 4614 mHeightCanMeasure = false; 4615 } 4616 } 4617 } else { 4618 mHeightCanMeasure = false; 4619 } 4620 if (mNativeClass != 0) { 4621 nativeSetHeightCanMeasure(mHeightCanMeasure); 4622 } 4623 // For the width, always use the given size unless unspecified. 4624 if (widthMode == MeasureSpec.UNSPECIFIED) { 4625 mWidthCanMeasure = true; 4626 measuredWidth = contentWidth; 4627 } else { 4628 mWidthCanMeasure = false; 4629 } 4630 4631 synchronized (this) { 4632 setMeasuredDimension(measuredWidth, measuredHeight); 4633 } 4634 } 4635 4636 @Override 4637 public boolean requestChildRectangleOnScreen(View child, 4638 Rect rect, 4639 boolean immediate) { 4640 rect.offset(child.getLeft() - child.getScrollX(), 4641 child.getTop() - child.getScrollY()); 4642 4643 int height = getHeight() - getHorizontalScrollbarHeight(); 4644 int screenTop = mScrollY; 4645 int screenBottom = screenTop + height; 4646 4647 int scrollYDelta = 0; 4648 4649 if (rect.bottom > screenBottom) { 4650 int oneThirdOfScreenHeight = height / 3; 4651 if (rect.height() > 2 * oneThirdOfScreenHeight) { 4652 // If the rectangle is too tall to fit in the bottom two thirds 4653 // of the screen, place it at the top. 4654 scrollYDelta = rect.top - screenTop; 4655 } else { 4656 // If the rectangle will still fit on screen, we want its 4657 // top to be in the top third of the screen. 4658 scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight); 4659 } 4660 } else if (rect.top < screenTop) { 4661 scrollYDelta = rect.top - screenTop; 4662 } 4663 4664 int width = getWidth() - getVerticalScrollbarWidth(); 4665 int screenLeft = mScrollX; 4666 int screenRight = screenLeft + width; 4667 4668 int scrollXDelta = 0; 4669 4670 if (rect.right > screenRight && rect.left > screenLeft) { 4671 if (rect.width() > width) { 4672 scrollXDelta += (rect.left - screenLeft); 4673 } else { 4674 scrollXDelta += (rect.right - screenRight); 4675 } 4676 } else if (rect.left < screenLeft) { 4677 scrollXDelta -= (screenLeft - rect.left); 4678 } 4679 4680 if ((scrollYDelta | scrollXDelta) != 0) { 4681 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0); 4682 } 4683 4684 return false; 4685 } 4686 4687 /* package */ void replaceTextfieldText(int oldStart, int oldEnd, 4688 String replace, int newStart, int newEnd) { 4689 WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData(); 4690 arg.mReplace = replace; 4691 arg.mNewStart = newStart; 4692 arg.mNewEnd = newEnd; 4693 mTextGeneration++; 4694 arg.mTextGeneration = mTextGeneration; 4695 mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); 4696 } 4697 4698 /* package */ void passToJavaScript(String currentText, KeyEvent event) { 4699 if (nativeCursorWantsKeyEvents() && !nativeCursorMatchesFocus()) { 4700 mWebViewCore.sendMessage(EventHub.CLICK); 4701 } 4702 WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData(); 4703 arg.mEvent = event; 4704 arg.mCurrentText = currentText; 4705 // Increase our text generation number, and pass it to webcore thread 4706 mTextGeneration++; 4707 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); 4708 // WebKit's document state is not saved until about to leave the page. 4709 // To make sure the host application, like Browser, has the up to date 4710 // document state when it goes to background, we force to save the 4711 // document state. 4712 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); 4713 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, 4714 cursorData(), 1000); 4715 } 4716 4717 /* package */ WebViewCore getWebViewCore() { 4718 return mWebViewCore; 4719 } 4720 4721 //------------------------------------------------------------------------- 4722 // Methods can be called from a separate thread, like WebViewCore 4723 // If it needs to call the View system, it has to send message. 4724 //------------------------------------------------------------------------- 4725 4726 /** 4727 * General handler to receive message coming from webkit thread 4728 */ 4729 class PrivateHandler extends Handler { 4730 @Override 4731 public void handleMessage(Message msg) { 4732 if (DebugFlags.WEB_VIEW) { 4733 Log.v(LOGTAG, msg.what < REMEMBER_PASSWORD || msg.what 4734 > INVAL_RECT_MSG_ID ? Integer.toString(msg.what) 4735 : HandlerDebugString[msg.what - REMEMBER_PASSWORD]); 4736 } 4737 switch (msg.what) { 4738 case REMEMBER_PASSWORD: { 4739 mDatabase.setUsernamePassword( 4740 msg.getData().getString("host"), 4741 msg.getData().getString("username"), 4742 msg.getData().getString("password")); 4743 ((Message) msg.obj).sendToTarget(); 4744 break; 4745 } 4746 case NEVER_REMEMBER_PASSWORD: { 4747 mDatabase.setUsernamePassword( 4748 msg.getData().getString("host"), null, null); 4749 ((Message) msg.obj).sendToTarget(); 4750 break; 4751 } 4752 case SWITCH_TO_SHORTPRESS: { 4753 if (mTouchMode == TOUCH_INIT_MODE) { 4754 mTouchMode = TOUCH_SHORTPRESS_START_MODE; 4755 updateSelection(); 4756 } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 4757 mTouchMode = TOUCH_DONE_MODE; 4758 } 4759 break; 4760 } 4761 case SWITCH_TO_LONGPRESS: { 4762 if (!mPreventDrag) { 4763 mTouchMode = TOUCH_DONE_MODE; 4764 performLongClick(); 4765 rebuildWebTextView(); 4766 } 4767 break; 4768 } 4769 case RELEASE_SINGLE_TAP: { 4770 if (!mPreventDrag) { 4771 mTouchMode = TOUCH_DONE_MODE; 4772 doShortPress(); 4773 } 4774 break; 4775 } 4776 case SCROLL_BY_MSG_ID: 4777 setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj); 4778 break; 4779 case SYNC_SCROLL_TO_MSG_ID: 4780 if (mUserScroll) { 4781 // if user has scrolled explicitly, don't sync the 4782 // scroll position any more 4783 mUserScroll = false; 4784 break; 4785 } 4786 // fall through 4787 case SCROLL_TO_MSG_ID: 4788 if (setContentScrollTo(msg.arg1, msg.arg2)) { 4789 // if we can't scroll to the exact position due to pin, 4790 // send a message to WebCore to re-scroll when we get a 4791 // new picture 4792 mUserScroll = false; 4793 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, 4794 msg.arg1, msg.arg2); 4795 } 4796 break; 4797 case SPAWN_SCROLL_TO_MSG_ID: 4798 spawnContentScrollTo(msg.arg1, msg.arg2); 4799 break; 4800 case NEW_PICTURE_MSG_ID: { 4801 WebSettings settings = mWebViewCore.getSettings(); 4802 // called for new content 4803 final int viewWidth = getViewWidth(); 4804 final WebViewCore.DrawData draw = 4805 (WebViewCore.DrawData) msg.obj; 4806 final Point viewSize = draw.mViewPoint; 4807 boolean useWideViewport = settings.getUseWideViewPort(); 4808 WebViewCore.RestoreState restoreState = draw.mRestoreState; 4809 if (restoreState != null) { 4810 mInZoomOverview = false; 4811 mLastScale = restoreState.mTextWrapScale; 4812 if (restoreState.mMinScale == 0) { 4813 if (restoreState.mMobileSite) { 4814 if (draw.mMinPrefWidth > draw.mViewPoint.x) { 4815 mMinZoomScale = (float) viewWidth 4816 / draw.mMinPrefWidth; 4817 mMinZoomScaleFixed = false; 4818 } else { 4819 mMinZoomScale = mDefaultScale; 4820 mMinZoomScaleFixed = true; 4821 } 4822 } else { 4823 mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; 4824 mMinZoomScaleFixed = false; 4825 } 4826 } else { 4827 mMinZoomScale = restoreState.mMinScale; 4828 mMinZoomScaleFixed = true; 4829 } 4830 if (restoreState.mMaxScale == 0) { 4831 mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; 4832 } else { 4833 mMaxZoomScale = restoreState.mMaxScale; 4834 } 4835 setNewZoomScale(mLastScale, false); 4836 if (getTitleHeight() != 0 && restoreState.mScrollX == 0 4837 && restoreState.mScrollY == 0) { 4838 // If there is a title bar, and the page is being 4839 // restored to (0,0), do not scroll the title bar 4840 // off the page. 4841 abortAnimation(); 4842 scrollTo(0,0); 4843 } else { 4844 setContentScrollTo(restoreState.mScrollX, 4845 restoreState.mScrollY); 4846 } 4847 if (useWideViewport 4848 && settings.getLoadWithOverviewMode()) { 4849 if (restoreState.mViewScale == 0 4850 || (restoreState.mMobileSite 4851 && mMinZoomScale < mDefaultScale)) { 4852 mInZoomOverview = true; 4853 } 4854 } 4855 // As we are on a new page, remove the WebTextView. This 4856 // is necessary for page loads driven by webkit, and in 4857 // particular when the user was on a password field, so 4858 // the WebTextView was visible. 4859 clearTextEntry(); 4860 } 4861 // We update the layout (i.e. request a layout from the 4862 // view system) if the last view size that we sent to 4863 // WebCore matches the view size of the picture we just 4864 // received in the fixed dimension. 4865 final boolean updateLayout = viewSize.x == mLastWidthSent 4866 && viewSize.y == mLastHeightSent; 4867 recordNewContentSize(draw.mWidthHeight.x, 4868 draw.mWidthHeight.y, updateLayout); 4869 if (DebugFlags.WEB_VIEW) { 4870 Rect b = draw.mInvalRegion.getBounds(); 4871 Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + 4872 b.left+","+b.top+","+b.right+","+b.bottom+"}"); 4873 } 4874 invalidateContentRect(draw.mInvalRegion.getBounds()); 4875 if (mPictureListener != null) { 4876 mPictureListener.onNewPicture(WebView.this, capturePicture()); 4877 } 4878 if (useWideViewport) { 4879 mZoomOverviewWidth = Math.max(draw.mMinPrefWidth, 4880 draw.mViewPoint.x); 4881 } 4882 if (!mMinZoomScaleFixed) { 4883 mMinZoomScale = (float) viewWidth / mZoomOverviewWidth; 4884 } 4885 if (!mDrawHistory && mInZoomOverview) { 4886 // fit the content width to the current view. Ignore 4887 // the rounding error case. 4888 if (Math.abs((viewWidth * mInvActualScale) 4889 - mZoomOverviewWidth) > 1) { 4890 setNewZoomScale((float) viewWidth 4891 / mZoomOverviewWidth, false); 4892 } 4893 } 4894 break; 4895 } 4896 case WEBCORE_INITIALIZED_MSG_ID: 4897 // nativeCreate sets mNativeClass to a non-zero value 4898 nativeCreate(msg.arg1); 4899 break; 4900 case UPDATE_TEXTFIELD_TEXT_MSG_ID: 4901 // Make sure that the textfield is currently focused 4902 // and representing the same node as the pointer. 4903 if (inEditingMode() && 4904 mWebTextView.isSameTextField(msg.arg1)) { 4905 if (msg.getData().getBoolean("password")) { 4906 Spannable text = (Spannable) mWebTextView.getText(); 4907 int start = Selection.getSelectionStart(text); 4908 int end = Selection.getSelectionEnd(text); 4909 mWebTextView.setInPassword(true); 4910 // Restore the selection, which may have been 4911 // ruined by setInPassword. 4912 Spannable pword = 4913 (Spannable) mWebTextView.getText(); 4914 Selection.setSelection(pword, start, end); 4915 // If the text entry has created more events, ignore 4916 // this one. 4917 } else if (msg.arg2 == mTextGeneration) { 4918 mWebTextView.setTextAndKeepSelection( 4919 (String) msg.obj); 4920 } 4921 } 4922 break; 4923 case UPDATE_TEXT_SELECTION_MSG_ID: 4924 if (inEditingMode() 4925 && mWebTextView.isSameTextField(msg.arg1) 4926 && msg.arg2 == mTextGeneration) { 4927 WebViewCore.TextSelectionData tData 4928 = (WebViewCore.TextSelectionData) msg.obj; 4929 mWebTextView.setSelectionFromWebKit(tData.mStart, 4930 tData.mEnd); 4931 } 4932 break; 4933 case MOVE_OUT_OF_PLUGIN: 4934 if (nativePluginEatsNavKey()) { 4935 navHandledKey(msg.arg1, 1, false, 0, true); 4936 } 4937 break; 4938 case UPDATE_TEXT_ENTRY_MSG_ID: 4939 // this is sent after finishing resize in WebViewCore. Make 4940 // sure the text edit box is still on the screen. 4941 if (inEditingMode() && nativeCursorIsTextInput()) { 4942 mWebTextView.bringIntoView(); 4943 rebuildWebTextView(); 4944 } 4945 break; 4946 case CLEAR_TEXT_ENTRY: 4947 clearTextEntry(); 4948 break; 4949 case INVAL_RECT_MSG_ID: { 4950 Rect r = (Rect)msg.obj; 4951 if (r == null) { 4952 invalidate(); 4953 } else { 4954 // we need to scale r from content into view coords, 4955 // which viewInvalidate() does for us 4956 viewInvalidate(r.left, r.top, r.right, r.bottom); 4957 } 4958 break; 4959 } 4960 case REQUEST_FORM_DATA: 4961 AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj; 4962 if (mWebTextView.isSameTextField(msg.arg1)) { 4963 mWebTextView.setAdapterCustom(adapter); 4964 } 4965 break; 4966 case UPDATE_CLIPBOARD: 4967 String str = (String) msg.obj; 4968 if (DebugFlags.WEB_VIEW) { 4969 Log.v(LOGTAG, "UPDATE_CLIPBOARD " + str); 4970 } 4971 try { 4972 IClipboard clip = IClipboard.Stub.asInterface( 4973 ServiceManager.getService("clipboard")); 4974 clip.setClipboardText(str); 4975 } catch (android.os.RemoteException e) { 4976 Log.e(LOGTAG, "Clipboard failed", e); 4977 } 4978 break; 4979 case RESUME_WEBCORE_UPDATE: 4980 WebViewCore.resumeUpdate(mWebViewCore); 4981 break; 4982 4983 case LONG_PRESS_CENTER: 4984 // as this is shared by keydown and trackballdown, reset all 4985 // the states 4986 mGotCenterDown = false; 4987 mTrackballDown = false; 4988 // LONG_PRESS_CENTER is sent as a delayed message. If we 4989 // switch to windows overview, the WebView will be 4990 // temporarily removed from the view system. In that case, 4991 // do nothing. 4992 if (getParent() != null) { 4993 performLongClick(); 4994 } 4995 break; 4996 4997 case WEBCORE_NEED_TOUCH_EVENTS: 4998 mForwardTouchEvents = (msg.arg1 != 0); 4999 break; 5000 5001 case PREVENT_TOUCH_ID: 5002 if (msg.arg1 == MotionEvent.ACTION_DOWN) { 5003 mPreventDrag = msg.arg2 == 1; 5004 if (mPreventDrag) { 5005 mTouchMode = TOUCH_DONE_MODE; 5006 } 5007 } 5008 break; 5009 5010 case REQUEST_KEYBOARD: 5011 if (msg.arg1 == 0) { 5012 hideSoftKeyboard(); 5013 } else { 5014 displaySoftKeyboard(false); 5015 } 5016 break; 5017 5018 default: 5019 super.handleMessage(msg); 5020 break; 5021 } 5022 } 5023 } 5024 5025 // Class used to use a dropdown for a <select> element 5026 private class InvokeListBox implements Runnable { 5027 // Whether the listbox allows multiple selection. 5028 private boolean mMultiple; 5029 // Passed in to a list with multiple selection to tell 5030 // which items are selected. 5031 private int[] mSelectedArray; 5032 // Passed in to a list with single selection to tell 5033 // where the initial selection is. 5034 private int mSelection; 5035 5036 private Container[] mContainers; 5037 5038 // Need these to provide stable ids to my ArrayAdapter, 5039 // which normally does not have stable ids. (Bug 1250098) 5040 private class Container extends Object { 5041 String mString; 5042 boolean mEnabled; 5043 int mId; 5044 5045 public String toString() { 5046 return mString; 5047 } 5048 } 5049 5050 /** 5051 * Subclass ArrayAdapter so we can disable OptionGroupLabels, 5052 * and allow filtering. 5053 */ 5054 private class MyArrayListAdapter extends ArrayAdapter<Container> { 5055 public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { 5056 super(context, 5057 multiple ? com.android.internal.R.layout.select_dialog_multichoice : 5058 com.android.internal.R.layout.select_dialog_singlechoice, 5059 objects); 5060 } 5061 5062 @Override 5063 public boolean hasStableIds() { 5064 // AdapterView's onChanged method uses this to determine whether 5065 // to restore the old state. Return false so that the old (out 5066 // of date) state does not replace the new, valid state. 5067 return false; 5068 } 5069 5070 private Container item(int position) { 5071 if (position < 0 || position >= getCount()) { 5072 return null; 5073 } 5074 return (Container) getItem(position); 5075 } 5076 5077 @Override 5078 public long getItemId(int position) { 5079 Container item = item(position); 5080 if (item == null) { 5081 return -1; 5082 } 5083 return item.mId; 5084 } 5085 5086 @Override 5087 public boolean areAllItemsEnabled() { 5088 return false; 5089 } 5090 5091 @Override 5092 public boolean isEnabled(int position) { 5093 Container item = item(position); 5094 if (item == null) { 5095 return false; 5096 } 5097 return item.mEnabled; 5098 } 5099 } 5100 5101 private InvokeListBox(String[] array, 5102 boolean[] enabled, int[] selected) { 5103 mMultiple = true; 5104 mSelectedArray = selected; 5105 5106 int length = array.length; 5107 mContainers = new Container[length]; 5108 for (int i = 0; i < length; i++) { 5109 mContainers[i] = new Container(); 5110 mContainers[i].mString = array[i]; 5111 mContainers[i].mEnabled = enabled[i]; 5112 mContainers[i].mId = i; 5113 } 5114 } 5115 5116 private InvokeListBox(String[] array, boolean[] enabled, int 5117 selection) { 5118 mSelection = selection; 5119 mMultiple = false; 5120 5121 int length = array.length; 5122 mContainers = new Container[length]; 5123 for (int i = 0; i < length; i++) { 5124 mContainers[i] = new Container(); 5125 mContainers[i].mString = array[i]; 5126 mContainers[i].mEnabled = enabled[i]; 5127 mContainers[i].mId = i; 5128 } 5129 } 5130 5131 /* 5132 * Whenever the data set changes due to filtering, this class ensures 5133 * that the checked item remains checked. 5134 */ 5135 private class SingleDataSetObserver extends DataSetObserver { 5136 private long mCheckedId; 5137 private ListView mListView; 5138 private Adapter mAdapter; 5139 5140 /* 5141 * Create a new observer. 5142 * @param id The ID of the item to keep checked. 5143 * @param l ListView for getting and clearing the checked states 5144 * @param a Adapter for getting the IDs 5145 */ 5146 public SingleDataSetObserver(long id, ListView l, Adapter a) { 5147 mCheckedId = id; 5148 mListView = l; 5149 mAdapter = a; 5150 } 5151 5152 public void onChanged() { 5153 // The filter may have changed which item is checked. Find the 5154 // item that the ListView thinks is checked. 5155 int position = mListView.getCheckedItemPosition(); 5156 long id = mAdapter.getItemId(position); 5157 if (mCheckedId != id) { 5158 // Clear the ListView's idea of the checked item, since 5159 // it is incorrect 5160 mListView.clearChoices(); 5161 // Search for mCheckedId. If it is in the filtered list, 5162 // mark it as checked 5163 int count = mAdapter.getCount(); 5164 for (int i = 0; i < count; i++) { 5165 if (mAdapter.getItemId(i) == mCheckedId) { 5166 mListView.setItemChecked(i, true); 5167 break; 5168 } 5169 } 5170 } 5171 } 5172 5173 public void onInvalidate() {} 5174 } 5175 5176 public void run() { 5177 final ListView listView = (ListView) LayoutInflater.from(mContext) 5178 .inflate(com.android.internal.R.layout.select_dialog, null); 5179 final MyArrayListAdapter adapter = new 5180 MyArrayListAdapter(mContext, mContainers, mMultiple); 5181 AlertDialog.Builder b = new AlertDialog.Builder(mContext) 5182 .setView(listView).setCancelable(true) 5183 .setInverseBackgroundForced(true); 5184 5185 if (mMultiple) { 5186 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 5187 public void onClick(DialogInterface dialog, int which) { 5188 mWebViewCore.sendMessage( 5189 EventHub.LISTBOX_CHOICES, 5190 adapter.getCount(), 0, 5191 listView.getCheckedItemPositions()); 5192 }}); 5193 b.setNegativeButton(android.R.string.cancel, 5194 new DialogInterface.OnClickListener() { 5195 public void onClick(DialogInterface dialog, int which) { 5196 mWebViewCore.sendMessage( 5197 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 5198 }}); 5199 } 5200 final AlertDialog dialog = b.create(); 5201 listView.setAdapter(adapter); 5202 listView.setFocusableInTouchMode(true); 5203 // There is a bug (1250103) where the checks in a ListView with 5204 // multiple items selected are associated with the positions, not 5205 // the ids, so the items do not properly retain their checks when 5206 // filtered. Do not allow filtering on multiple lists until 5207 // that bug is fixed. 5208 5209 listView.setTextFilterEnabled(!mMultiple); 5210 if (mMultiple) { 5211 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 5212 int length = mSelectedArray.length; 5213 for (int i = 0; i < length; i++) { 5214 listView.setItemChecked(mSelectedArray[i], true); 5215 } 5216 } else { 5217 listView.setOnItemClickListener(new OnItemClickListener() { 5218 public void onItemClick(AdapterView parent, View v, 5219 int position, long id) { 5220 mWebViewCore.sendMessage( 5221 EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0); 5222 dialog.dismiss(); 5223 } 5224 }); 5225 if (mSelection != -1) { 5226 listView.setSelection(mSelection); 5227 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 5228 listView.setItemChecked(mSelection, true); 5229 DataSetObserver observer = new SingleDataSetObserver( 5230 adapter.getItemId(mSelection), listView, adapter); 5231 adapter.registerDataSetObserver(observer); 5232 } 5233 } 5234 dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 5235 public void onCancel(DialogInterface dialog) { 5236 mWebViewCore.sendMessage( 5237 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 5238 } 5239 }); 5240 dialog.show(); 5241 } 5242 } 5243 5244 /* 5245 * Request a dropdown menu for a listbox with multiple selection. 5246 * 5247 * @param array Labels for the listbox. 5248 * @param enabledArray Which positions are enabled. 5249 * @param selectedArray Which positions are initally selected. 5250 */ 5251 void requestListBox(String[] array, boolean[]enabledArray, int[] 5252 selectedArray) { 5253 mPrivateHandler.post( 5254 new InvokeListBox(array, enabledArray, selectedArray)); 5255 } 5256 5257 /* 5258 * Request a dropdown menu for a listbox with single selection or a single 5259 * <select> element. 5260 * 5261 * @param array Labels for the listbox. 5262 * @param enabledArray Which positions are enabled. 5263 * @param selection Which position is initally selected. 5264 */ 5265 void requestListBox(String[] array, boolean[]enabledArray, int selection) { 5266 mPrivateHandler.post( 5267 new InvokeListBox(array, enabledArray, selection)); 5268 } 5269 5270 // called by JNI 5271 private void sendMoveMouse(int frame, int node, int x, int y) { 5272 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, 5273 new WebViewCore.CursorData(frame, node, x, y)); 5274 } 5275 5276 /* 5277 * Send a mouse move event to the webcore thread. 5278 * 5279 * @param removeFocus Pass true if the "mouse" cursor is now over a node 5280 * which wants key events, but it is not the focus. This 5281 * will make the visual appear as though nothing is in 5282 * focus. Remove the WebTextView, if present, and stop 5283 * drawing the blinking caret. 5284 * called by JNI 5285 */ 5286 private void sendMoveMouseIfLatest(boolean removeFocus) { 5287 if (removeFocus) { 5288 clearTextEntry(); 5289 setFocusControllerInactive(); 5290 } 5291 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, 5292 cursorData()); 5293 } 5294 5295 // called by JNI 5296 private void sendMotionUp(int touchGeneration, 5297 int frame, int node, int x, int y) { 5298 WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); 5299 touchUpData.mMoveGeneration = touchGeneration; 5300 touchUpData.mFrame = frame; 5301 touchUpData.mNode = node; 5302 touchUpData.mX = x; 5303 touchUpData.mY = y; 5304 mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); 5305 } 5306 5307 5308 private int getScaledMaxXScroll() { 5309 int width; 5310 if (mHeightCanMeasure == false) { 5311 width = getViewWidth() / 4; 5312 } else { 5313 Rect visRect = new Rect(); 5314 calcOurVisibleRect(visRect); 5315 width = visRect.width() / 2; 5316 } 5317 // FIXME the divisor should be retrieved from somewhere 5318 return viewToContentX(width); 5319 } 5320 5321 private int getScaledMaxYScroll() { 5322 int height; 5323 if (mHeightCanMeasure == false) { 5324 height = getViewHeight() / 4; 5325 } else { 5326 Rect visRect = new Rect(); 5327 calcOurVisibleRect(visRect); 5328 height = visRect.height() / 2; 5329 } 5330 // FIXME the divisor should be retrieved from somewhere 5331 // the closest thing today is hard-coded into ScrollView.java 5332 // (from ScrollView.java, line 363) int maxJump = height/2; 5333 return Math.round(height * mInvActualScale); 5334 } 5335 5336 /** 5337 * Called by JNI to invalidate view 5338 */ 5339 private void viewInvalidate() { 5340 invalidate(); 5341 } 5342 5343 // return true if the key was handled 5344 private boolean navHandledKey(int keyCode, int count, boolean noScroll, 5345 long time, boolean ignorePlugin) { 5346 if (mNativeClass == 0) { 5347 return false; 5348 } 5349 if (ignorePlugin == false && nativePluginEatsNavKey()) { 5350 KeyEvent event = new KeyEvent(time, time, KeyEvent.ACTION_DOWN 5351 , keyCode, count, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0) 5352 | (false ? KeyEvent.META_ALT_ON : 0) // FIXME 5353 | (false ? KeyEvent.META_SYM_ON : 0) // FIXME 5354 , 0, 0, 0); 5355 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 5356 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 5357 return true; 5358 } 5359 mLastCursorTime = time; 5360 mLastCursorBounds = nativeGetCursorRingBounds(); 5361 boolean keyHandled 5362 = nativeMoveCursor(keyCode, count, noScroll) == false; 5363 if (DebugFlags.WEB_VIEW) { 5364 Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds 5365 + " mLastCursorTime=" + mLastCursorTime 5366 + " handled=" + keyHandled); 5367 } 5368 if (keyHandled == false || mHeightCanMeasure == false) { 5369 return keyHandled; 5370 } 5371 Rect contentCursorRingBounds = nativeGetCursorRingBounds(); 5372 if (contentCursorRingBounds.isEmpty()) return keyHandled; 5373 Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds); 5374 Rect visRect = new Rect(); 5375 calcOurVisibleRect(visRect); 5376 Rect outset = new Rect(visRect); 5377 int maxXScroll = visRect.width() / 2; 5378 int maxYScroll = visRect.height() / 2; 5379 outset.inset(-maxXScroll, -maxYScroll); 5380 if (Rect.intersects(outset, viewCursorRingBounds) == false) { 5381 return keyHandled; 5382 } 5383 // FIXME: Necessary because ScrollView/ListView do not scroll left/right 5384 int maxH = Math.min(viewCursorRingBounds.right - visRect.right, 5385 maxXScroll); 5386 if (maxH > 0) { 5387 pinScrollBy(maxH, 0, true, 0); 5388 } else { 5389 maxH = Math.max(viewCursorRingBounds.left - visRect.left, 5390 -maxXScroll); 5391 if (maxH < 0) { 5392 pinScrollBy(maxH, 0, true, 0); 5393 } 5394 } 5395 if (mLastCursorBounds.isEmpty()) return keyHandled; 5396 if (mLastCursorBounds.equals(contentCursorRingBounds)) { 5397 return keyHandled; 5398 } 5399 if (DebugFlags.WEB_VIEW) { 5400 Log.v(LOGTAG, "navHandledKey contentCursorRingBounds=" 5401 + contentCursorRingBounds); 5402 } 5403 requestRectangleOnScreen(viewCursorRingBounds); 5404 mUserScroll = true; 5405 return keyHandled; 5406 } 5407 5408 /** 5409 * Set the background color. It's white by default. Pass 5410 * zero to make the view transparent. 5411 * @param color the ARGB color described by Color.java 5412 */ 5413 public void setBackgroundColor(int color) { 5414 mBackgroundColor = color; 5415 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color); 5416 } 5417 5418 public void debugDump() { 5419 nativeDebugDump(); 5420 mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); 5421 } 5422 5423 /** 5424 * Update our cache with updatedText. 5425 * @param updatedText The new text to put in our cache. 5426 */ 5427 /* package */ void updateCachedTextfield(String updatedText) { 5428 // Also place our generation number so that when we look at the cache 5429 // we recognize that it is up to date. 5430 nativeUpdateCachedTextfield(updatedText, mTextGeneration); 5431 } 5432 5433 /* package */ native void nativeClearCursor(); 5434 private native void nativeCreate(int ptr); 5435 private native int nativeCursorFramePointer(); 5436 private native Rect nativeCursorNodeBounds(); 5437 /* package */ native int nativeCursorNodePointer(); 5438 /* package */ native boolean nativeCursorMatchesFocus(); 5439 private native boolean nativeCursorIntersects(Rect visibleRect); 5440 private native boolean nativeCursorIsAnchor(); 5441 private native boolean nativeCursorIsPlugin(); 5442 private native boolean nativeCursorIsTextInput(); 5443 private native Point nativeCursorPosition(); 5444 private native String nativeCursorText(); 5445 /** 5446 * Returns true if the native cursor node says it wants to handle key events 5447 * (ala plugins). This can only be called if mNativeClass is non-zero! 5448 */ 5449 private native boolean nativeCursorWantsKeyEvents(); 5450 private native void nativeDebugDump(); 5451 private native void nativeDestroy(); 5452 private native void nativeDrawCursorRing(Canvas content); 5453 private native void nativeDrawMatches(Canvas canvas); 5454 private native void nativeDrawSelection(Canvas content 5455 , int x, int y, boolean extendSelection); 5456 private native void nativeDrawSelectionRegion(Canvas content); 5457 private native void nativeDumpDisplayTree(String urlOrNull); 5458 private native int nativeFindAll(String findLower, String findUpper); 5459 private native void nativeFindNext(boolean forward); 5460 private native boolean nativeFocusCandidateIsPassword(); 5461 private native boolean nativeFocusCandidateIsRtlText(); 5462 private native boolean nativeFocusCandidateIsTextField(); 5463 private native boolean nativeFocusCandidateIsTextInput(); 5464 private native int nativeFocusCandidateMaxLength(); 5465 /* package */ native String nativeFocusCandidateName(); 5466 private native Rect nativeFocusCandidateNodeBounds(); 5467 /* package */ native int nativeFocusCandidatePointer(); 5468 private native String nativeFocusCandidateText(); 5469 private native int nativeFocusCandidateTextSize(); 5470 /* package */ native int nativeFocusNodePointer(); 5471 private native Rect nativeGetCursorRingBounds(); 5472 private native Region nativeGetSelection(); 5473 private native boolean nativeHasCursorNode(); 5474 private native boolean nativeHasFocusNode(); 5475 private native void nativeHideCursor(); 5476 private native String nativeImageURI(int x, int y); 5477 private native void nativeInstrumentReport(); 5478 /* package */ native void nativeMoveCursorToNextTextInput(); 5479 // return true if the page has been scrolled 5480 private native boolean nativeMotionUp(int x, int y, int slop); 5481 // returns false if it handled the key 5482 private native boolean nativeMoveCursor(int keyCode, int count, 5483 boolean noScroll); 5484 private native int nativeMoveGeneration(); 5485 private native void nativeMoveSelection(int x, int y, 5486 boolean extendSelection); 5487 private native boolean nativePluginEatsNavKey(); 5488 // Like many other of our native methods, you must make sure that 5489 // mNativeClass is not null before calling this method. 5490 private native void nativeRecordButtons(boolean focused, 5491 boolean pressed, boolean invalidate); 5492 private native void nativeSelectBestAt(Rect rect); 5493 private native void nativeSetFindIsDown(); 5494 private native void nativeSetFollowedLink(boolean followed); 5495 private native void nativeSetHeightCanMeasure(boolean measure); 5496 // Returns a value corresponding to CachedFrame::ImeAction 5497 /* package */ native int nativeTextFieldAction(); 5498 /** 5499 * Perform a click on a currently focused text input. Since it is already 5500 * focused, there is no need to go through the nativeMotionUp code, which 5501 * may change the Cursor. 5502 */ 5503 private native void nativeTextInputMotionUp(int x, int y); 5504 private native int nativeTextGeneration(); 5505 // Never call this version except by updateCachedTextfield(String) - 5506 // we always want to pass in our generation number. 5507 private native void nativeUpdateCachedTextfield(String updatedText, 5508 int generation); 5509 private native void nativeUpdatePluginReceivesEvents(); 5510 // return NO_LEFTEDGE means failure. 5511 private static final int NO_LEFTEDGE = -1; 5512 private native int nativeGetBlockLeftEdge(int x, int y, float scale); 5513} 5514