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