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