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