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