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