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