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