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