WebView.java revision 90b6e4879304c6ac8240cdeb9a4aea94a27cda58
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.annotation.Widget; 20import android.app.AlertDialog; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.Intent; 24import android.content.DialogInterface.OnCancelListener; 25import android.content.pm.PackageManager; 26import android.database.DataSetObserver; 27import android.graphics.Bitmap; 28import android.graphics.Canvas; 29import android.graphics.Color; 30import android.graphics.Interpolator; 31import android.graphics.Picture; 32import android.graphics.Point; 33import android.graphics.Rect; 34import android.graphics.RectF; 35import android.graphics.drawable.Drawable; 36import android.net.Uri; 37import android.net.http.SslCertificate; 38import android.os.Bundle; 39import android.os.Handler; 40import android.os.Message; 41import android.os.ServiceManager; 42import android.os.SystemClock; 43import android.speech.tts.TextToSpeech; 44import android.text.IClipboard; 45import android.text.Selection; 46import android.text.Spannable; 47import android.text.TextUtils; 48import android.util.AttributeSet; 49import android.util.EventLog; 50import android.util.Log; 51import android.util.TypedValue; 52import android.view.Gravity; 53import android.view.KeyEvent; 54import android.view.LayoutInflater; 55import android.view.MotionEvent; 56import android.view.ScaleGestureDetector; 57import android.view.SoundEffectConstants; 58import android.view.VelocityTracker; 59import android.view.View; 60import android.view.ViewConfiguration; 61import android.view.ViewGroup; 62import android.view.ViewTreeObserver; 63import android.view.accessibility.AccessibilityManager; 64import android.view.animation.AlphaAnimation; 65import android.view.inputmethod.EditorInfo; 66import android.view.inputmethod.InputConnection; 67import android.view.inputmethod.InputMethodManager; 68import android.webkit.WebTextView.AutoCompleteAdapter; 69import android.webkit.WebViewCore.EventHub; 70import android.webkit.WebViewCore.TouchEventData; 71import android.widget.AbsoluteLayout; 72import android.widget.Adapter; 73import android.widget.AdapterView; 74import android.widget.ArrayAdapter; 75import android.widget.CheckedTextView; 76import android.widget.LinearLayout; 77import android.widget.ListView; 78import android.widget.Scroller; 79import android.widget.Toast; 80import android.widget.ZoomButtonsController; 81import android.widget.AdapterView.OnItemClickListener; 82 83import java.io.File; 84import java.io.FileInputStream; 85import java.io.FileNotFoundException; 86import java.io.FileOutputStream; 87import java.net.URLDecoder; 88import java.util.ArrayList; 89import java.util.HashMap; 90import java.util.List; 91import java.util.Map; 92import java.util.Set; 93 94import junit.framework.Assert; 95 96/** 97 * <p>A View that displays web pages. This class is the basis upon which you 98 * can roll your own web browser or simply display some online content within your Activity. 99 * It uses the WebKit rendering engine to display 100 * web pages and includes methods to navigate forward and backward 101 * through a history, zoom in and out, perform text searches and more.</p> 102 * <p>To enable the built-in zoom, set 103 * {@link #getSettings() WebSettings}.{@link WebSettings#setBuiltInZoomControls(boolean)} 104 * (introduced in API version 3). 105 * <p>Note that, in order for your Activity to access the Internet and load web pages 106 * in a WebView, you must add the <var>INTERNET</var> permissions to your 107 * Android Manifest file:</p> 108 * <pre><uses-permission android:name="android.permission.INTERNET" /></pre> 109 * 110 * <p>This must be a child of the <code><manifest></code> element.</p> 111 * 112 * <h3>Basic usage</h3> 113 * 114 * <p>By default, a WebView provides no browser-like widgets, does not 115 * enable JavaScript and errors will be ignored. If your goal is only 116 * to display some HTML as a part of your UI, this is probably fine; 117 * the user won't need to interact with the web page beyond reading 118 * it, and the web page won't need to interact with the user. If you 119 * actually want a fully blown web browser, then you probably want to 120 * invoke the Browser application with your URL rather than show it 121 * with a WebView. See {@link android.content.Intent} for more information.</p> 122 * 123 * <pre class="prettyprint"> 124 * WebView webview = new WebView(this); 125 * setContentView(webview); 126 * 127 * // Simplest usage: note that an exception will NOT be thrown 128 * // if there is an error loading this page (see below). 129 * webview.loadUrl("http://slashdot.org/"); 130 * 131 * // Of course you can also load from any string: 132 * String summary = "<html><body>You scored <b>192</b> points.</body></html>"; 133 * webview.loadData(summary, "text/html", "utf-8"); 134 * // ... although note that there are restrictions on what this HTML can do. 135 * // See the JavaDocs for loadData and loadDataWithBaseUrl for more info. 136 * </pre> 137 * 138 * <p>A WebView has several customization points where you can add your 139 * own behavior. These are:</p> 140 * 141 * <ul> 142 * <li>Creating and setting a {@link android.webkit.WebChromeClient} subclass. 143 * This class is called when something that might impact a 144 * browser UI happens, for instance, progress updates and 145 * JavaScript alerts are sent here. 146 * </li> 147 * <li>Creating and setting a {@link android.webkit.WebViewClient} subclass. 148 * It will be called when things happen that impact the 149 * rendering of the content, eg, errors or form submissions. You 150 * can also intercept URL loading here.</li> 151 * <li>Via the {@link android.webkit.WebSettings} class, which contains 152 * miscellaneous configuration. </li> 153 * <li>With the {@link android.webkit.WebView#addJavascriptInterface} method. 154 * This lets you bind Java objects into the WebView so they can be 155 * controlled from the web pages JavaScript.</li> 156 * </ul> 157 * 158 * <p>Here's a more complicated example, showing error handling, 159 * settings, and progress notification:</p> 160 * 161 * <pre class="prettyprint"> 162 * // Let's display the progress in the activity title bar, like the 163 * // browser app does. 164 * getWindow().requestFeature(Window.FEATURE_PROGRESS); 165 * 166 * webview.getSettings().setJavaScriptEnabled(true); 167 * 168 * final Activity activity = this; 169 * webview.setWebChromeClient(new WebChromeClient() { 170 * public void onProgressChanged(WebView view, int progress) { 171 * // Activities and WebViews measure progress with different scales. 172 * // The progress meter will automatically disappear when we reach 100% 173 * activity.setProgress(progress * 1000); 174 * } 175 * }); 176 * webview.setWebViewClient(new WebViewClient() { 177 * public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) { 178 * Toast.makeText(activity, "Oh no! " + description, Toast.LENGTH_SHORT).show(); 179 * } 180 * }); 181 * 182 * webview.loadUrl("http://slashdot.org/"); 183 * </pre> 184 * 185 * <h3>Cookie and window management</h3> 186 * 187 * <p>For obvious security reasons, your application has its own 188 * cache, cookie store etc - it does not share the Browser 189 * applications data. Cookies are managed on a separate thread, so 190 * operations like index building don't block the UI 191 * thread. Follow the instructions in {@link android.webkit.CookieSyncManager} 192 * if you want to use cookies in your application. 193 * </p> 194 * 195 * <p>By default, requests by the HTML to open new windows are 196 * ignored. This is true whether they be opened by JavaScript or by 197 * the target attribute on a link. You can customize your 198 * WebChromeClient to provide your own behaviour for opening multiple windows, 199 * and render them in whatever manner you want.</p> 200 * 201 * <p>Standard behavior for an Activity is to be destroyed and 202 * recreated when the devices orientation is changed. This will cause 203 * the WebView to reload the current page. If you don't want that, you 204 * can set your Activity to handle the orientation and keyboardHidden 205 * changes, and then just leave the WebView alone. It'll automatically 206 * re-orient itself as appropriate.</p> 207 */ 208@Widget 209public class WebView extends AbsoluteLayout 210 implements ViewTreeObserver.OnGlobalFocusChangeListener, 211 ViewGroup.OnHierarchyChangeListener { 212 213 // enable debug output for drag trackers 214 private static final boolean DEBUG_DRAG_TRACKER = false; 215 // if AUTO_REDRAW_HACK is true, then the CALL key will toggle redrawing 216 // the screen all-the-time. Good for profiling our drawing code 217 static private final boolean AUTO_REDRAW_HACK = false; 218 // true means redraw the screen all-the-time. Only with AUTO_REDRAW_HACK 219 private boolean mAutoRedraw; 220 221 static final String LOGTAG = "webview"; 222 223 private ZoomManager mZoomManager; 224 225 /** 226 * Transportation object for returning WebView across thread boundaries. 227 */ 228 public class WebViewTransport { 229 private WebView mWebview; 230 231 /** 232 * Set the WebView to the transportation object. 233 * @param webview The WebView to transport. 234 */ 235 public synchronized void setWebView(WebView webview) { 236 mWebview = webview; 237 } 238 239 /** 240 * Return the WebView object. 241 * @return WebView The transported WebView object. 242 */ 243 public synchronized WebView getWebView() { 244 return mWebview; 245 } 246 } 247 248 // A final CallbackProxy shared by WebViewCore and BrowserFrame. 249 private final CallbackProxy mCallbackProxy; 250 251 private final WebViewDatabase mDatabase; 252 253 // SSL certificate for the main top-level page (if secure) 254 private SslCertificate mCertificate; 255 256 // Native WebView pointer that is 0 until the native object has been 257 // created. 258 private int mNativeClass; 259 // This would be final but it needs to be set to null when the WebView is 260 // destroyed. 261 private WebViewCore mWebViewCore; 262 // Handler for dispatching UI messages. 263 /* package */ final Handler mPrivateHandler = new PrivateHandler(); 264 private WebTextView mWebTextView; 265 // Used to ignore changes to webkit text that arrives to the UI side after 266 // more key events. 267 private int mTextGeneration; 268 269 /* package */ void incrementTextGeneration() { mTextGeneration++; } 270 271 // Used by WebViewCore to create child views. 272 /* package */ final ViewManager mViewManager; 273 274 // Used to display in full screen mode 275 PluginFullScreenHolder mFullScreenHolder; 276 277 /** 278 * Position of the last touch event. 279 */ 280 private float mLastTouchX; 281 private float mLastTouchY; 282 283 /** 284 * Time of the last touch event. 285 */ 286 private long mLastTouchTime; 287 288 /** 289 * Time of the last time sending touch event to WebViewCore 290 */ 291 private long mLastSentTouchTime; 292 293 /** 294 * The minimum elapsed time before sending another ACTION_MOVE event to 295 * WebViewCore. This really should be tuned for each type of the devices. 296 * For example in Google Map api test case, it takes Dream device at least 297 * 150ms to do a full cycle in the WebViewCore by processing a touch event, 298 * triggering the layout and drawing the picture. While the same process 299 * takes 60+ms on the current high speed device. If we make 300 * TOUCH_SENT_INTERVAL too small, there will be multiple touch events sent 301 * to WebViewCore queue and the real layout and draw events will be pushed 302 * to further, which slows down the refresh rate. Choose 50 to favor the 303 * current high speed devices. For Dream like devices, 100 is a better 304 * choice. Maybe make this in the buildspec later. 305 */ 306 private static final int TOUCH_SENT_INTERVAL = 50; 307 private int mCurrentTouchInterval = TOUCH_SENT_INTERVAL; 308 309 /** 310 * Helper class to get velocity for fling 311 */ 312 VelocityTracker mVelocityTracker; 313 private int mMaximumFling; 314 private float mLastVelocity; 315 private float mLastVelX; 316 private float mLastVelY; 317 318 /** 319 * Touch mode 320 */ 321 private int mTouchMode = TOUCH_DONE_MODE; 322 private static final int TOUCH_INIT_MODE = 1; 323 private static final int TOUCH_DRAG_START_MODE = 2; 324 private static final int TOUCH_DRAG_MODE = 3; 325 private static final int TOUCH_SHORTPRESS_START_MODE = 4; 326 private static final int TOUCH_SHORTPRESS_MODE = 5; 327 private static final int TOUCH_DOUBLE_TAP_MODE = 6; 328 private static final int TOUCH_DONE_MODE = 7; 329 private static final int TOUCH_SELECT_MODE = 8; 330 private static final int TOUCH_PINCH_DRAG = 9; 331 332 // Whether to forward the touch events to WebCore 333 private boolean mForwardTouchEvents = false; 334 335 // Whether to prevent default during touch. The initial value depends on 336 // mForwardTouchEvents. If WebCore wants all the touch events, it says yes 337 // for touch down. Otherwise UI will wait for the answer of the first 338 // confirmed move before taking over the control. 339 private static final int PREVENT_DEFAULT_NO = 0; 340 private static final int PREVENT_DEFAULT_MAYBE_YES = 1; 341 private static final int PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN = 2; 342 private static final int PREVENT_DEFAULT_YES = 3; 343 private static final int PREVENT_DEFAULT_IGNORE = 4; 344 private int mPreventDefault = PREVENT_DEFAULT_IGNORE; 345 346 // true when the touch movement exceeds the slop 347 private boolean mConfirmMove; 348 349 // if true, touch events will be first processed by WebCore, if prevent 350 // default is not set, the UI will continue handle them. 351 private boolean mDeferTouchProcess; 352 353 // to avoid interfering with the current touch events, track them 354 // separately. Currently no snapping or fling in the deferred process mode 355 private int mDeferTouchMode = TOUCH_DONE_MODE; 356 private float mLastDeferTouchX; 357 private float mLastDeferTouchY; 358 359 // To keep track of whether the current drag was initiated by a WebTextView, 360 // so that we know not to hide the cursor 361 boolean mDragFromTextInput; 362 363 // Whether or not to draw the cursor ring. 364 private boolean mDrawCursorRing = true; 365 366 // true if onPause has been called (and not onResume) 367 private boolean mIsPaused; 368 369 // true if, during a transition to a new page, we're delaying 370 // deleting a root layer until there's something to draw of the new page. 371 private boolean mDelayedDeleteRootLayer; 372 373 /** 374 * Customizable constant 375 */ 376 // pre-computed square of ViewConfiguration.getScaledTouchSlop() 377 private int mTouchSlopSquare; 378 // pre-computed square of ViewConfiguration.getScaledDoubleTapSlop() 379 private int mDoubleTapSlopSquare; 380 // pre-computed density adjusted navigation slop 381 private int mNavSlop; 382 // This should be ViewConfiguration.getTapTimeout() 383 // But system time out is 100ms, which is too short for the browser. 384 // In the browser, if it switches out of tap too soon, jump tap won't work. 385 private static final int TAP_TIMEOUT = 200; 386 // This should be ViewConfiguration.getLongPressTimeout() 387 // But system time out is 500ms, which is too short for the browser. 388 // With a short timeout, it's difficult to treat trigger a short press. 389 private static final int LONG_PRESS_TIMEOUT = 1000; 390 // needed to avoid flinging after a pause of no movement 391 private static final int MIN_FLING_TIME = 250; 392 // draw unfiltered after drag is held without movement 393 private static final int MOTIONLESS_TIME = 100; 394 // The amount of content to overlap between two screens when going through 395 // pages with the space bar, in pixels. 396 private static final int PAGE_SCROLL_OVERLAP = 24; 397 398 /** 399 * These prevent calling requestLayout if either dimension is fixed. This 400 * depends on the layout parameters and the measure specs. 401 */ 402 boolean mWidthCanMeasure; 403 boolean mHeightCanMeasure; 404 405 // Remember the last dimensions we sent to the native side so we can avoid 406 // sending the same dimensions more than once. 407 int mLastWidthSent; 408 int mLastHeightSent; 409 410 private int mContentWidth; // cache of value from WebViewCore 411 private int mContentHeight; // cache of value from WebViewCore 412 413 // Need to have the separate control for horizontal and vertical scrollbar 414 // style than the View's single scrollbar style 415 private boolean mOverlayHorizontalScrollbar = true; 416 private boolean mOverlayVerticalScrollbar = false; 417 418 // our standard speed. this way small distances will be traversed in less 419 // time than large distances, but we cap the duration, so that very large 420 // distances won't take too long to get there. 421 private static final int STD_SPEED = 480; // pixels per second 422 // time for the longest scroll animation 423 private static final int MAX_DURATION = 750; // milliseconds 424 private static final int SLIDE_TITLE_DURATION = 500; // milliseconds 425 private Scroller mScroller; 426 427 private boolean mWrapContent; 428 private static final int MOTIONLESS_FALSE = 0; 429 private static final int MOTIONLESS_PENDING = 1; 430 private static final int MOTIONLESS_TRUE = 2; 431 private static final int MOTIONLESS_IGNORE = 3; 432 private int mHeldMotionless; 433 434 // whether support multi-touch 435 private boolean mSupportMultiTouch; 436 // use the framework's ScaleGestureDetector to handle multi-touch 437 private ScaleGestureDetector mScaleDetector; 438 439 // An instance for injecting accessibility in WebViews with disabled 440 // JavaScript or ones for which no accessibility script exists 441 private AccessibilityInjector mAccessibilityInjector; 442 443 // the anchor point in the document space where VIEW_SIZE_CHANGED should 444 // apply to 445 private int mAnchorX; 446 private int mAnchorY; 447 448 /* 449 * Private message ids 450 */ 451 private static final int REMEMBER_PASSWORD = 1; 452 private static final int NEVER_REMEMBER_PASSWORD = 2; 453 private static final int SWITCH_TO_SHORTPRESS = 3; 454 private static final int SWITCH_TO_LONGPRESS = 4; 455 private static final int RELEASE_SINGLE_TAP = 5; 456 private static final int REQUEST_FORM_DATA = 6; 457 private static final int RESUME_WEBCORE_PRIORITY = 7; 458 private static final int DRAG_HELD_MOTIONLESS = 8; 459 private static final int AWAKEN_SCROLL_BARS = 9; 460 private static final int PREVENT_DEFAULT_TIMEOUT = 10; 461 462 private static final int FIRST_PRIVATE_MSG_ID = REMEMBER_PASSWORD; 463 private static final int LAST_PRIVATE_MSG_ID = PREVENT_DEFAULT_TIMEOUT; 464 465 /* 466 * Package message ids 467 */ 468 //! arg1=x, arg2=y 469 static final int SCROLL_TO_MSG_ID = 101; 470 static final int SCROLL_BY_MSG_ID = 102; 471 //! arg1=x, arg2=y 472 static final int SPAWN_SCROLL_TO_MSG_ID = 103; 473 //! arg1=x, arg2=y 474 static final int SYNC_SCROLL_TO_MSG_ID = 104; 475 static final int NEW_PICTURE_MSG_ID = 105; 476 static final int UPDATE_TEXT_ENTRY_MSG_ID = 106; 477 static final int WEBCORE_INITIALIZED_MSG_ID = 107; 478 static final int UPDATE_TEXTFIELD_TEXT_MSG_ID = 108; 479 static final int UPDATE_ZOOM_RANGE = 109; 480 static final int UNHANDLED_NAV_KEY = 110; 481 static final int CLEAR_TEXT_ENTRY = 111; 482 static final int UPDATE_TEXT_SELECTION_MSG_ID = 112; 483 static final int SHOW_RECT_MSG_ID = 113; 484 static final int LONG_PRESS_CENTER = 114; 485 static final int PREVENT_TOUCH_ID = 115; 486 static final int WEBCORE_NEED_TOUCH_EVENTS = 116; 487 // obj=Rect in doc coordinates 488 static final int INVAL_RECT_MSG_ID = 117; 489 static final int REQUEST_KEYBOARD = 118; 490 static final int DO_MOTION_UP = 119; 491 static final int SHOW_FULLSCREEN = 120; 492 static final int HIDE_FULLSCREEN = 121; 493 static final int DOM_FOCUS_CHANGED = 122; 494 static final int IMMEDIATE_REPAINT_MSG_ID = 123; 495 static final int SET_ROOT_LAYER_MSG_ID = 124; 496 static final int RETURN_LABEL = 125; 497 static final int FIND_AGAIN = 126; 498 static final int CENTER_FIT_RECT = 127; 499 static final int REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID = 128; 500 static final int SET_SCROLLBAR_MODES = 129; 501 static final int SELECTION_STRING_CHANGED = 130; 502 503 private static final int FIRST_PACKAGE_MSG_ID = SCROLL_TO_MSG_ID; 504 private static final int LAST_PACKAGE_MSG_ID = SET_SCROLLBAR_MODES; 505 506 static final String[] HandlerPrivateDebugString = { 507 "REMEMBER_PASSWORD", // = 1; 508 "NEVER_REMEMBER_PASSWORD", // = 2; 509 "SWITCH_TO_SHORTPRESS", // = 3; 510 "SWITCH_TO_LONGPRESS", // = 4; 511 "RELEASE_SINGLE_TAP", // = 5; 512 "REQUEST_FORM_DATA", // = 6; 513 "RESUME_WEBCORE_PRIORITY", // = 7; 514 "DRAG_HELD_MOTIONLESS", // = 8; 515 "AWAKEN_SCROLL_BARS", // = 9; 516 "PREVENT_DEFAULT_TIMEOUT" // = 10; 517 }; 518 519 static final String[] HandlerPackageDebugString = { 520 "SCROLL_TO_MSG_ID", // = 101; 521 "SCROLL_BY_MSG_ID", // = 102; 522 "SPAWN_SCROLL_TO_MSG_ID", // = 103; 523 "SYNC_SCROLL_TO_MSG_ID", // = 104; 524 "NEW_PICTURE_MSG_ID", // = 105; 525 "UPDATE_TEXT_ENTRY_MSG_ID", // = 106; 526 "WEBCORE_INITIALIZED_MSG_ID", // = 107; 527 "UPDATE_TEXTFIELD_TEXT_MSG_ID", // = 108; 528 "UPDATE_ZOOM_RANGE", // = 109; 529 "UNHANDLED_NAV_KEY", // = 110; 530 "CLEAR_TEXT_ENTRY", // = 111; 531 "UPDATE_TEXT_SELECTION_MSG_ID", // = 112; 532 "SHOW_RECT_MSG_ID", // = 113; 533 "LONG_PRESS_CENTER", // = 114; 534 "PREVENT_TOUCH_ID", // = 115; 535 "WEBCORE_NEED_TOUCH_EVENTS", // = 116; 536 "INVAL_RECT_MSG_ID", // = 117; 537 "REQUEST_KEYBOARD", // = 118; 538 "DO_MOTION_UP", // = 119; 539 "SHOW_FULLSCREEN", // = 120; 540 "HIDE_FULLSCREEN", // = 121; 541 "DOM_FOCUS_CHANGED", // = 122; 542 "IMMEDIATE_REPAINT_MSG_ID", // = 123; 543 "SET_ROOT_LAYER_MSG_ID", // = 124; 544 "RETURN_LABEL", // = 125; 545 "FIND_AGAIN", // = 126; 546 "CENTER_FIT_RECT", // = 127; 547 "REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID", // = 128; 548 "SET_SCROLLBAR_MODES" // = 129; 549 }; 550 551 // If the site doesn't use the viewport meta tag to specify the viewport, 552 // use DEFAULT_VIEWPORT_WIDTH as the default viewport width 553 static final int DEFAULT_VIEWPORT_WIDTH = 800; 554 555 // normally we try to fit the content to the minimum preferred width 556 // calculated by the Webkit. To avoid the bad behavior when some site's 557 // minimum preferred width keeps growing when changing the viewport width or 558 // the minimum preferred width is huge, an upper limit is needed. 559 static int sMaxViewportWidth = DEFAULT_VIEWPORT_WIDTH; 560 561 // initial scale in percent. 0 means using default. 562 private int mInitialScaleInPercent = 0; 563 564 // ideally mZoomOverviewWidth should be mContentWidth. But sites like espn, 565 // engadget always have wider mContentWidth no matter what viewport size is. 566 int mZoomOverviewWidth = DEFAULT_VIEWPORT_WIDTH; 567 float mTextWrapScale; 568 569 // default scale. Depending on the display density. 570 static int DEFAULT_SCALE_PERCENT; 571 private float mDefaultScale; 572 573 private static float MINIMUM_SCALE_INCREMENT = 0.01f; 574 575 // set to true temporarily during ScaleGesture triggered zoom 576 private boolean mPreviewZoomOnly = false; 577 578 // computed scale and inverse, from mZoomWidth. 579 private float mActualScale; 580 private float mInvActualScale; 581 // if this is non-zero, it is used on drawing rather than mActualScale 582 private float mZoomScale; 583 private float mInvInitialZoomScale; 584 private float mInvFinalZoomScale; 585 private int mInitialScrollX; 586 private int mInitialScrollY; 587 private long mZoomStart; 588 private static final int ZOOM_ANIMATION_LENGTH = 500; 589 590 private boolean mUserScroll = false; 591 592 private int mSnapScrollMode = SNAP_NONE; 593 private static final int SNAP_NONE = 0; 594 private static final int SNAP_LOCK = 1; // not a separate state 595 private static final int SNAP_X = 2; // may be combined with SNAP_LOCK 596 private static final int SNAP_Y = 4; // may be combined with SNAP_LOCK 597 private boolean mSnapPositive; 598 599 // keep these in sync with their counterparts in WebView.cpp 600 private static final int DRAW_EXTRAS_NONE = 0; 601 private static final int DRAW_EXTRAS_FIND = 1; 602 private static final int DRAW_EXTRAS_SELECTION = 2; 603 private static final int DRAW_EXTRAS_CURSOR_RING = 3; 604 605 // keep this in sync with WebCore:ScrollbarMode in WebKit 606 private static final int SCROLLBAR_AUTO = 0; 607 private static final int SCROLLBAR_ALWAYSOFF = 1; 608 // as we auto fade scrollbar, this is ignored. 609 private static final int SCROLLBAR_ALWAYSON = 2; 610 private int mHorizontalScrollBarMode = SCROLLBAR_AUTO; 611 private int mVerticalScrollBarMode = SCROLLBAR_AUTO; 612 613 // the alias via which accessibility JavaScript interface is exposed 614 private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility"; 615 616 // JavaScript to inject the script chooser which will 617 // pick the right script for the current URL 618 private static final String ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT = 619 "javascript:(function() {" + 620 " var chooser = document.createElement('script');" + 621 " chooser.type = 'text/javascript';" + 622 " chooser.src = 'https://ssl.gstatic.com/accessibility/javascript/android/AndroidScriptChooser.user.js';" + 623 " document.getElementsByTagName('head')[0].appendChild(chooser);" + 624 " })();"; 625 626 // Used to match key downs and key ups 627 private boolean mGotKeyDown; 628 629 /* package */ static boolean mLogEvent = true; 630 631 // for event log 632 private long mLastTouchUpTime = 0; 633 634 /** 635 * URI scheme for telephone number 636 */ 637 public static final String SCHEME_TEL = "tel:"; 638 /** 639 * URI scheme for email address 640 */ 641 public static final String SCHEME_MAILTO = "mailto:"; 642 /** 643 * URI scheme for map address 644 */ 645 public static final String SCHEME_GEO = "geo:0,0?q="; 646 647 private int mBackgroundColor = Color.WHITE; 648 649 // Used to notify listeners of a new picture. 650 private PictureListener mPictureListener; 651 /** 652 * Interface to listen for new pictures as they change. 653 */ 654 public interface PictureListener { 655 /** 656 * Notify the listener that the picture has changed. 657 * @param view The WebView that owns the picture. 658 * @param picture The new picture. 659 */ 660 public void onNewPicture(WebView view, Picture picture); 661 } 662 663 // FIXME: Want to make this public, but need to change the API file. 664 public /*static*/ class HitTestResult { 665 /** 666 * Default HitTestResult, where the target is unknown 667 */ 668 public static final int UNKNOWN_TYPE = 0; 669 /** 670 * HitTestResult for hitting a HTML::a tag 671 */ 672 public static final int ANCHOR_TYPE = 1; 673 /** 674 * HitTestResult for hitting a phone number 675 */ 676 public static final int PHONE_TYPE = 2; 677 /** 678 * HitTestResult for hitting a map address 679 */ 680 public static final int GEO_TYPE = 3; 681 /** 682 * HitTestResult for hitting an email address 683 */ 684 public static final int EMAIL_TYPE = 4; 685 /** 686 * HitTestResult for hitting an HTML::img tag 687 */ 688 public static final int IMAGE_TYPE = 5; 689 /** 690 * HitTestResult for hitting a HTML::a tag which contains HTML::img 691 */ 692 public static final int IMAGE_ANCHOR_TYPE = 6; 693 /** 694 * HitTestResult for hitting a HTML::a tag with src=http 695 */ 696 public static final int SRC_ANCHOR_TYPE = 7; 697 /** 698 * HitTestResult for hitting a HTML::a tag with src=http + HTML::img 699 */ 700 public static final int SRC_IMAGE_ANCHOR_TYPE = 8; 701 /** 702 * HitTestResult for hitting an edit text area 703 */ 704 public static final int EDIT_TEXT_TYPE = 9; 705 706 private int mType; 707 private String mExtra; 708 709 HitTestResult() { 710 mType = UNKNOWN_TYPE; 711 } 712 713 private void setType(int type) { 714 mType = type; 715 } 716 717 private void setExtra(String extra) { 718 mExtra = extra; 719 } 720 721 public int getType() { 722 return mType; 723 } 724 725 public String getExtra() { 726 return mExtra; 727 } 728 } 729 730 // These keep track of the center point of the zoom. They are used to 731 // determine the point around which we should zoom. 732 private float mZoomCenterX; 733 private float mZoomCenterY; 734 735 /** 736 * Construct a new WebView with a Context object. 737 * @param context A Context object used to access application assets. 738 */ 739 public WebView(Context context) { 740 this(context, null); 741 } 742 743 /** 744 * Construct a new WebView with layout parameters. 745 * @param context A Context object used to access application assets. 746 * @param attrs An AttributeSet passed to our parent. 747 */ 748 public WebView(Context context, AttributeSet attrs) { 749 this(context, attrs, com.android.internal.R.attr.webViewStyle); 750 } 751 752 /** 753 * Construct a new WebView with layout parameters and a default style. 754 * @param context A Context object used to access application assets. 755 * @param attrs An AttributeSet passed to our parent. 756 * @param defStyle The default style resource ID. 757 */ 758 public WebView(Context context, AttributeSet attrs, int defStyle) { 759 this(context, attrs, defStyle, null); 760 } 761 762 /** 763 * Construct a new WebView with layout parameters, a default style and a set 764 * of custom Javscript interfaces to be added to the WebView at initialization 765 * time. This guarantees that these interfaces will be available when the JS 766 * context is initialized. 767 * @param context A Context object used to access application assets. 768 * @param attrs An AttributeSet passed to our parent. 769 * @param defStyle The default style resource ID. 770 * @param javascriptInterfaces is a Map of interface names, as keys, and 771 * object implementing those interfaces, as values. 772 * @hide pending API council approval. 773 */ 774 protected WebView(Context context, AttributeSet attrs, int defStyle, 775 Map<String, Object> javascriptInterfaces) { 776 super(context, attrs, defStyle); 777 778 if (AccessibilityManager.getInstance(context).isEnabled()) { 779 if (javascriptInterfaces == null) { 780 javascriptInterfaces = new HashMap<String, Object>(); 781 } 782 exposeAccessibilityJavaScriptApi(javascriptInterfaces); 783 } 784 785 mCallbackProxy = new CallbackProxy(context, this); 786 mViewManager = new ViewManager(this); 787 mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); 788 mDatabase = WebViewDatabase.getInstance(context); 789 mScroller = new Scroller(context); 790 mZoomManager = new ZoomManager(this); 791 792 /* The init method must follow the creation of certain member variables, 793 * such as the mZoomManager. 794 */ 795 init(); 796 updateMultiTouchSupport(context); 797 } 798 799 void updateMultiTouchSupport(Context context) { 800 WebSettings settings = getSettings(); 801 mSupportMultiTouch = context.getPackageManager().hasSystemFeature( 802 PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) 803 && settings.supportZoom() && settings.getBuiltInZoomControls(); 804 if (mSupportMultiTouch && (mScaleDetector == null)) { 805 mScaleDetector = new ScaleGestureDetector(context, 806 new ScaleDetectorListener()); 807 } else if (!mSupportMultiTouch && (mScaleDetector != null)) { 808 mScaleDetector = null; 809 } 810 } 811 812 private void init() { 813 setWillNotDraw(false); 814 setFocusable(true); 815 setFocusableInTouchMode(true); 816 setClickable(true); 817 setLongClickable(true); 818 819 final ViewConfiguration configuration = ViewConfiguration.get(getContext()); 820 int slop = configuration.getScaledTouchSlop(); 821 mTouchSlopSquare = slop * slop; 822 mMinLockSnapReverseDistance = slop; 823 slop = configuration.getScaledDoubleTapSlop(); 824 mDoubleTapSlopSquare = slop * slop; 825 final float density = getContext().getResources().getDisplayMetrics().density; 826 // use one line height, 16 based on our current default font, for how 827 // far we allow a touch be away from the edge of a link 828 mNavSlop = (int) (16 * density); 829 // density adjusted scale factors 830 DEFAULT_SCALE_PERCENT = (int) (100 * density); 831 mDefaultScale = density; 832 mActualScale = density; 833 mInvActualScale = 1 / density; 834 mTextWrapScale = density; 835 mZoomManager.init(density); 836 mMaximumFling = configuration.getScaledMaximumFlingVelocity(); 837 } 838 839 /** 840 * Exposes accessibility APIs to JavaScript by appending them to the JavaScript 841 * interfaces map provided by the WebView client. In case of conflicting 842 * alias with the one of the accessibility API the user specified one wins. 843 * 844 * @param javascriptInterfaces A map with interfaces to be exposed to JavaScript. 845 */ 846 private void exposeAccessibilityJavaScriptApi(Map<String, Object> javascriptInterfaces) { 847 if (javascriptInterfaces.containsKey(ALIAS_ACCESSIBILITY_JS_INTERFACE)) { 848 Log.w(LOGTAG, "JavaScript interface mapped to \"" + ALIAS_ACCESSIBILITY_JS_INTERFACE 849 + "\" overrides the accessibility API JavaScript interface. No accessibility" 850 + "API will be exposed to JavaScript!"); 851 return; 852 } 853 854 // expose the TTS for now ... 855 javascriptInterfaces.put(ALIAS_ACCESSIBILITY_JS_INTERFACE, 856 new TextToSpeech(getContext(), null)); 857 } 858 859 /* package */void updateDefaultZoomDensity(int zoomDensity) { 860 final float density = getContext().getResources().getDisplayMetrics().density 861 * 100 / zoomDensity; 862 if (Math.abs(density - mDefaultScale) > 0.01) { 863 float scaleFactor = density / mDefaultScale; 864 // adjust the limits 865 mNavSlop = (int) (16 * density); 866 DEFAULT_SCALE_PERCENT = (int) (100 * density); 867 mZoomManager.DEFAULT_MAX_ZOOM_SCALE = 4.0f * density; 868 mZoomManager.DEFAULT_MIN_ZOOM_SCALE = 0.25f * density; 869 mDefaultScale = density; 870 mZoomManager.mMaxZoomScale *= scaleFactor; 871 mZoomManager.mMinZoomScale *= scaleFactor; 872 setNewZoomScale(mActualScale * scaleFactor, true, false); 873 } 874 } 875 876 /* package */ boolean onSavePassword(String schemePlusHost, String username, 877 String password, final Message resumeMsg) { 878 boolean rVal = false; 879 if (resumeMsg == null) { 880 // null resumeMsg implies saving password silently 881 mDatabase.setUsernamePassword(schemePlusHost, username, password); 882 } else { 883 final Message remember = mPrivateHandler.obtainMessage( 884 REMEMBER_PASSWORD); 885 remember.getData().putString("host", schemePlusHost); 886 remember.getData().putString("username", username); 887 remember.getData().putString("password", password); 888 remember.obj = resumeMsg; 889 890 final Message neverRemember = mPrivateHandler.obtainMessage( 891 NEVER_REMEMBER_PASSWORD); 892 neverRemember.getData().putString("host", schemePlusHost); 893 neverRemember.getData().putString("username", username); 894 neverRemember.getData().putString("password", password); 895 neverRemember.obj = resumeMsg; 896 897 new AlertDialog.Builder(getContext()) 898 .setTitle(com.android.internal.R.string.save_password_label) 899 .setMessage(com.android.internal.R.string.save_password_message) 900 .setPositiveButton(com.android.internal.R.string.save_password_notnow, 901 new DialogInterface.OnClickListener() { 902 public void onClick(DialogInterface dialog, int which) { 903 resumeMsg.sendToTarget(); 904 } 905 }) 906 .setNeutralButton(com.android.internal.R.string.save_password_remember, 907 new DialogInterface.OnClickListener() { 908 public void onClick(DialogInterface dialog, int which) { 909 remember.sendToTarget(); 910 } 911 }) 912 .setNegativeButton(com.android.internal.R.string.save_password_never, 913 new DialogInterface.OnClickListener() { 914 public void onClick(DialogInterface dialog, int which) { 915 neverRemember.sendToTarget(); 916 } 917 }) 918 .setOnCancelListener(new OnCancelListener() { 919 public void onCancel(DialogInterface dialog) { 920 resumeMsg.sendToTarget(); 921 } 922 }).show(); 923 // Return true so that WebViewCore will pause while the dialog is 924 // up. 925 rVal = true; 926 } 927 return rVal; 928 } 929 930 @Override 931 public void setScrollBarStyle(int style) { 932 if (style == View.SCROLLBARS_INSIDE_INSET 933 || style == View.SCROLLBARS_OUTSIDE_INSET) { 934 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = false; 935 } else { 936 mOverlayHorizontalScrollbar = mOverlayVerticalScrollbar = true; 937 } 938 super.setScrollBarStyle(style); 939 } 940 941 /** 942 * Specify whether the horizontal scrollbar has overlay style. 943 * @param overlay TRUE if horizontal scrollbar should have overlay style. 944 */ 945 public void setHorizontalScrollbarOverlay(boolean overlay) { 946 mOverlayHorizontalScrollbar = overlay; 947 } 948 949 /** 950 * Specify whether the vertical scrollbar has overlay style. 951 * @param overlay TRUE if vertical scrollbar should have overlay style. 952 */ 953 public void setVerticalScrollbarOverlay(boolean overlay) { 954 mOverlayVerticalScrollbar = overlay; 955 } 956 957 /** 958 * Return whether horizontal scrollbar has overlay style 959 * @return TRUE if horizontal scrollbar has overlay style. 960 */ 961 public boolean overlayHorizontalScrollbar() { 962 return mOverlayHorizontalScrollbar; 963 } 964 965 /** 966 * Return whether vertical scrollbar has overlay style 967 * @return TRUE if vertical scrollbar has overlay style. 968 */ 969 public boolean overlayVerticalScrollbar() { 970 return mOverlayVerticalScrollbar; 971 } 972 973 /* 974 * Return the width of the view where the content of WebView should render 975 * to. 976 * Note: this can be called from WebCoreThread. 977 */ 978 /* package */ int getViewWidth() { 979 if (!isVerticalScrollBarEnabled() || mOverlayVerticalScrollbar) { 980 return getWidth(); 981 } else { 982 return getWidth() - getVerticalScrollbarWidth(); 983 } 984 } 985 986 /* 987 * returns the height of the titlebarview (if any). Does not care about 988 * scrolling 989 */ 990 private int getTitleHeight() { 991 return mTitleBar != null ? mTitleBar.getHeight() : 0; 992 } 993 994 /* 995 * Return the amount of the titlebarview (if any) that is visible 996 */ 997 private int getVisibleTitleHeight() { 998 return Math.max(getTitleHeight() - mScrollY, 0); 999 } 1000 1001 /* 1002 * Return the height of the view where the content of WebView should render 1003 * to. Note that this excludes mTitleBar, if there is one. 1004 * Note: this can be called from WebCoreThread. 1005 */ 1006 /* package */ int getViewHeight() { 1007 return getViewHeightWithTitle() - getVisibleTitleHeight(); 1008 } 1009 1010 private int getViewHeightWithTitle() { 1011 int height = getHeight(); 1012 if (isHorizontalScrollBarEnabled() && !mOverlayHorizontalScrollbar) { 1013 height -= getHorizontalScrollbarHeight(); 1014 } 1015 return height; 1016 } 1017 1018 /** 1019 * @return The SSL certificate for the main top-level page or null if 1020 * there is no certificate (the site is not secure). 1021 */ 1022 public SslCertificate getCertificate() { 1023 return mCertificate; 1024 } 1025 1026 /** 1027 * Sets the SSL certificate for the main top-level page. 1028 */ 1029 public void setCertificate(SslCertificate certificate) { 1030 if (DebugFlags.WEB_VIEW) { 1031 Log.v(LOGTAG, "setCertificate=" + certificate); 1032 } 1033 // here, the certificate can be null (if the site is not secure) 1034 mCertificate = certificate; 1035 } 1036 1037 //------------------------------------------------------------------------- 1038 // Methods called by activity 1039 //------------------------------------------------------------------------- 1040 1041 /** 1042 * Save the username and password for a particular host in the WebView's 1043 * internal database. 1044 * @param host The host that required the credentials. 1045 * @param username The username for the given host. 1046 * @param password The password for the given host. 1047 */ 1048 public void savePassword(String host, String username, String password) { 1049 mDatabase.setUsernamePassword(host, username, password); 1050 } 1051 1052 /** 1053 * Set the HTTP authentication credentials for a given host and realm. 1054 * 1055 * @param host The host for the credentials. 1056 * @param realm The realm for the credentials. 1057 * @param username The username for the password. If it is null, it means 1058 * password can't be saved. 1059 * @param password The password 1060 */ 1061 public void setHttpAuthUsernamePassword(String host, String realm, 1062 String username, String password) { 1063 mDatabase.setHttpAuthUsernamePassword(host, realm, username, password); 1064 } 1065 1066 /** 1067 * Retrieve the HTTP authentication username and password for a given 1068 * host & realm pair 1069 * 1070 * @param host The host for which the credentials apply. 1071 * @param realm The realm for which the credentials apply. 1072 * @return String[] if found, String[0] is username, which can be null and 1073 * String[1] is password. Return null if it can't find anything. 1074 */ 1075 public String[] getHttpAuthUsernamePassword(String host, String realm) { 1076 return mDatabase.getHttpAuthUsernamePassword(host, realm); 1077 } 1078 1079 /** 1080 * Destroy the internal state of the WebView. This method should be called 1081 * after the WebView has been removed from the view system. No other 1082 * methods may be called on a WebView after destroy. 1083 */ 1084 public void destroy() { 1085 clearTextEntry(false); 1086 if (mWebViewCore != null) { 1087 // Set the handlers to null before destroying WebViewCore so no 1088 // more messages will be posted. 1089 mCallbackProxy.setWebViewClient(null); 1090 mCallbackProxy.setWebChromeClient(null); 1091 // Tell WebViewCore to destroy itself 1092 synchronized (this) { 1093 WebViewCore webViewCore = mWebViewCore; 1094 mWebViewCore = null; // prevent using partial webViewCore 1095 webViewCore.destroy(); 1096 } 1097 // Remove any pending messages that might not be serviced yet. 1098 mPrivateHandler.removeCallbacksAndMessages(null); 1099 mCallbackProxy.removeCallbacksAndMessages(null); 1100 // Wake up the WebCore thread just in case it is waiting for a 1101 // javascript dialog. 1102 synchronized (mCallbackProxy) { 1103 mCallbackProxy.notify(); 1104 } 1105 } 1106 if (mNativeClass != 0) { 1107 nativeDestroy(); 1108 mNativeClass = 0; 1109 } 1110 } 1111 1112 /** 1113 * Enables platform notifications of data state and proxy changes. 1114 */ 1115 public static void enablePlatformNotifications() { 1116 Network.enablePlatformNotifications(); 1117 } 1118 1119 /** 1120 * If platform notifications are enabled, this should be called 1121 * from the Activity's onPause() or onStop(). 1122 */ 1123 public static void disablePlatformNotifications() { 1124 Network.disablePlatformNotifications(); 1125 } 1126 1127 /** 1128 * Sets JavaScript engine flags. 1129 * 1130 * @param flags JS engine flags in a String 1131 * 1132 * @hide pending API solidification 1133 */ 1134 public void setJsFlags(String flags) { 1135 mWebViewCore.sendMessage(EventHub.SET_JS_FLAGS, flags); 1136 } 1137 1138 /** 1139 * Inform WebView of the network state. This is used to set 1140 * the javascript property window.navigator.isOnline and 1141 * generates the online/offline event as specified in HTML5, sec. 5.7.7 1142 * @param networkUp boolean indicating if network is available 1143 */ 1144 public void setNetworkAvailable(boolean networkUp) { 1145 mWebViewCore.sendMessage(EventHub.SET_NETWORK_STATE, 1146 networkUp ? 1 : 0, 0); 1147 } 1148 1149 /** 1150 * Inform WebView about the current network type. 1151 * {@hide} 1152 */ 1153 public void setNetworkType(String type, String subtype) { 1154 Map<String, String> map = new HashMap<String, String>(); 1155 map.put("type", type); 1156 map.put("subtype", subtype); 1157 mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map); 1158 } 1159 /** 1160 * Save the state of this WebView used in 1161 * {@link android.app.Activity#onSaveInstanceState}. Please note that this 1162 * method no longer stores the display data for this WebView. The previous 1163 * behavior could potentially leak files if {@link #restoreState} was never 1164 * called. See {@link #savePicture} and {@link #restorePicture} for saving 1165 * and restoring the display data. 1166 * @param outState The Bundle to store the WebView state. 1167 * @return The same copy of the back/forward list used to save the state. If 1168 * saveState fails, the returned list will be null. 1169 * @see #savePicture 1170 * @see #restorePicture 1171 */ 1172 public WebBackForwardList saveState(Bundle outState) { 1173 if (outState == null) { 1174 return null; 1175 } 1176 // We grab a copy of the back/forward list because a client of WebView 1177 // may have invalidated the history list by calling clearHistory. 1178 WebBackForwardList list = copyBackForwardList(); 1179 final int currentIndex = list.getCurrentIndex(); 1180 final int size = list.getSize(); 1181 // We should fail saving the state if the list is empty or the index is 1182 // not in a valid range. 1183 if (currentIndex < 0 || currentIndex >= size || size == 0) { 1184 return null; 1185 } 1186 outState.putInt("index", currentIndex); 1187 // FIXME: This should just be a byte[][] instead of ArrayList but 1188 // Parcel.java does not have the code to handle multi-dimensional 1189 // arrays. 1190 ArrayList<byte[]> history = new ArrayList<byte[]>(size); 1191 for (int i = 0; i < size; i++) { 1192 WebHistoryItem item = list.getItemAtIndex(i); 1193 if (null == item) { 1194 // FIXME: this shouldn't happen 1195 // need to determine how item got set to null 1196 Log.w(LOGTAG, "saveState: Unexpected null history item."); 1197 return null; 1198 } 1199 byte[] data = item.getFlattenedData(); 1200 if (data == null) { 1201 // It would be very odd to not have any data for a given history 1202 // item. And we will fail to rebuild the history list without 1203 // flattened data. 1204 return null; 1205 } 1206 history.add(data); 1207 } 1208 outState.putSerializable("history", history); 1209 if (mCertificate != null) { 1210 outState.putBundle("certificate", 1211 SslCertificate.saveState(mCertificate)); 1212 } 1213 return list; 1214 } 1215 1216 /** 1217 * Save the current display data to the Bundle given. Used in conjunction 1218 * with {@link #saveState}. 1219 * @param b A Bundle to store the display data. 1220 * @param dest The file to store the serialized picture data. Will be 1221 * overwritten with this WebView's picture data. 1222 * @return True if the picture was successfully saved. 1223 */ 1224 public boolean savePicture(Bundle b, final File dest) { 1225 if (dest == null || b == null) { 1226 return false; 1227 } 1228 final Picture p = capturePicture(); 1229 // Use a temporary file while writing to ensure the destination file 1230 // contains valid data. 1231 final File temp = new File(dest.getPath() + ".writing"); 1232 new Thread(new Runnable() { 1233 public void run() { 1234 try { 1235 FileOutputStream out = new FileOutputStream(temp); 1236 p.writeToStream(out); 1237 out.close(); 1238 // Writing the picture succeeded, rename the temporary file 1239 // to the destination. 1240 temp.renameTo(dest); 1241 } catch (Exception e) { 1242 // too late to do anything about it. 1243 } finally { 1244 temp.delete(); 1245 } 1246 } 1247 }).start(); 1248 // now update the bundle 1249 b.putInt("scrollX", mScrollX); 1250 b.putInt("scrollY", mScrollY); 1251 b.putFloat("scale", mActualScale); 1252 b.putFloat("textwrapScale", mTextWrapScale); 1253 b.putBoolean("overview", mZoomManager.mInZoomOverview); 1254 return true; 1255 } 1256 1257 private void restoreHistoryPictureFields(Picture p, Bundle b) { 1258 int sx = b.getInt("scrollX", 0); 1259 int sy = b.getInt("scrollY", 0); 1260 float scale = b.getFloat("scale", 1.0f); 1261 mDrawHistory = true; 1262 mHistoryPicture = p; 1263 mScrollX = sx; 1264 mScrollY = sy; 1265 mHistoryWidth = Math.round(p.getWidth() * scale); 1266 mHistoryHeight = Math.round(p.getHeight() * scale); 1267 // as getWidth() / getHeight() of the view are not available yet, set up 1268 // mActualScale, so that when onSizeChanged() is called, the rest will 1269 // be set correctly 1270 mActualScale = scale; 1271 mInvActualScale = 1 / scale; 1272 mTextWrapScale = b.getFloat("textwrapScale", scale); 1273 mZoomManager.mInZoomOverview = b.getBoolean("overview"); 1274 invalidate(); 1275 } 1276 1277 /** 1278 * Restore the display data that was save in {@link #savePicture}. Used in 1279 * conjunction with {@link #restoreState}. 1280 * @param b A Bundle containing the saved display data. 1281 * @param src The file where the picture data was stored. 1282 * @return True if the picture was successfully restored. 1283 */ 1284 public boolean restorePicture(Bundle b, File src) { 1285 if (src == null || b == null) { 1286 return false; 1287 } 1288 if (!src.exists()) { 1289 return false; 1290 } 1291 try { 1292 final FileInputStream in = new FileInputStream(src); 1293 final Bundle copy = new Bundle(b); 1294 new Thread(new Runnable() { 1295 public void run() { 1296 final Picture p = Picture.createFromStream(in); 1297 if (p != null) { 1298 // Post a runnable on the main thread to update the 1299 // history picture fields. 1300 mPrivateHandler.post(new Runnable() { 1301 public void run() { 1302 restoreHistoryPictureFields(p, copy); 1303 } 1304 }); 1305 } 1306 try { 1307 in.close(); 1308 } catch (Exception e) { 1309 // Nothing we can do now. 1310 } 1311 } 1312 }).start(); 1313 } catch (FileNotFoundException e){ 1314 e.printStackTrace(); 1315 } 1316 return true; 1317 } 1318 1319 /** 1320 * Restore the state of this WebView from the given map used in 1321 * {@link android.app.Activity#onRestoreInstanceState}. This method should 1322 * be called to restore the state of the WebView before using the object. If 1323 * it is called after the WebView has had a chance to build state (load 1324 * pages, create a back/forward list, etc.) there may be undesirable 1325 * side-effects. Please note that this method no longer restores the 1326 * display data for this WebView. See {@link #savePicture} and {@link 1327 * #restorePicture} for saving and restoring the display data. 1328 * @param inState The incoming Bundle of state. 1329 * @return The restored back/forward list or null if restoreState failed. 1330 * @see #savePicture 1331 * @see #restorePicture 1332 */ 1333 public WebBackForwardList restoreState(Bundle inState) { 1334 WebBackForwardList returnList = null; 1335 if (inState == null) { 1336 return returnList; 1337 } 1338 if (inState.containsKey("index") && inState.containsKey("history")) { 1339 mCertificate = SslCertificate.restoreState( 1340 inState.getBundle("certificate")); 1341 1342 final WebBackForwardList list = mCallbackProxy.getBackForwardList(); 1343 final int index = inState.getInt("index"); 1344 // We can't use a clone of the list because we need to modify the 1345 // shared copy, so synchronize instead to prevent concurrent 1346 // modifications. 1347 synchronized (list) { 1348 final List<byte[]> history = 1349 (List<byte[]>) inState.getSerializable("history"); 1350 final int size = history.size(); 1351 // Check the index bounds so we don't crash in native code while 1352 // restoring the history index. 1353 if (index < 0 || index >= size) { 1354 return null; 1355 } 1356 for (int i = 0; i < size; i++) { 1357 byte[] data = history.remove(0); 1358 if (data == null) { 1359 // If we somehow have null data, we cannot reconstruct 1360 // the item and thus our history list cannot be rebuilt. 1361 return null; 1362 } 1363 WebHistoryItem item = new WebHistoryItem(data); 1364 list.addHistoryItem(item); 1365 } 1366 // Grab the most recent copy to return to the caller. 1367 returnList = copyBackForwardList(); 1368 // Update the copy to have the correct index. 1369 returnList.setCurrentIndex(index); 1370 } 1371 // Remove all pending messages because we are restoring previous 1372 // state. 1373 mWebViewCore.removeMessages(); 1374 // Send a restore state message. 1375 mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); 1376 } 1377 return returnList; 1378 } 1379 1380 /** 1381 * Load the given url with the extra headers. 1382 * @param url The url of the resource to load. 1383 * @param extraHeaders The extra headers sent with this url. This should not 1384 * include the common headers like "user-agent". If it does, it 1385 * will be replaced by the intrinsic value of the WebView. 1386 */ 1387 public void loadUrl(String url, Map<String, String> extraHeaders) { 1388 switchOutDrawHistory(); 1389 WebViewCore.GetUrlData arg = new WebViewCore.GetUrlData(); 1390 arg.mUrl = url; 1391 arg.mExtraHeaders = extraHeaders; 1392 mWebViewCore.sendMessage(EventHub.LOAD_URL, arg); 1393 clearTextEntry(false); 1394 } 1395 1396 /** 1397 * Load the given url. 1398 * @param url The url of the resource to load. 1399 */ 1400 public void loadUrl(String url) { 1401 if (url == null) { 1402 return; 1403 } 1404 loadUrl(url, null); 1405 } 1406 1407 /** 1408 * Load the url with postData using "POST" method into the WebView. If url 1409 * is not a network url, it will be loaded with {link 1410 * {@link #loadUrl(String)} instead. 1411 * 1412 * @param url The url of the resource to load. 1413 * @param postData The data will be passed to "POST" request. 1414 */ 1415 public void postUrl(String url, byte[] postData) { 1416 if (URLUtil.isNetworkUrl(url)) { 1417 switchOutDrawHistory(); 1418 WebViewCore.PostUrlData arg = new WebViewCore.PostUrlData(); 1419 arg.mUrl = url; 1420 arg.mPostData = postData; 1421 mWebViewCore.sendMessage(EventHub.POST_URL, arg); 1422 clearTextEntry(false); 1423 } else { 1424 loadUrl(url); 1425 } 1426 } 1427 1428 /** 1429 * Load the given data into the WebView. This will load the data into 1430 * WebView using the data: scheme. Content loaded through this mechanism 1431 * does not have the ability to load content from the network. 1432 * @param data A String of data in the given encoding. The date must 1433 * be URI-escaped -- '#', '%', '\', '?' should be replaced by %23, %25, 1434 * %27, %3f respectively. 1435 * @param mimeType The MIMEType of the data. i.e. text/html, image/jpeg 1436 * @param encoding The encoding of the data. i.e. utf-8, base64 1437 */ 1438 public void loadData(String data, String mimeType, String encoding) { 1439 loadUrl("data:" + mimeType + ";" + encoding + "," + data); 1440 } 1441 1442 /** 1443 * Load the given data into the WebView, use the provided URL as the base 1444 * URL for the content. The base URL is the URL that represents the page 1445 * that is loaded through this interface. As such, it is used to resolve any 1446 * relative URLs. The historyUrl is used for the history entry. 1447 * <p> 1448 * Note for post 1.0. Due to the change in the WebKit, the access to asset 1449 * files through "file:///android_asset/" for the sub resources is more 1450 * restricted. If you provide null or empty string as baseUrl, you won't be 1451 * able to access asset files. If the baseUrl is anything other than 1452 * http(s)/ftp(s)/about/javascript as scheme, you can access asset files for 1453 * sub resources. 1454 * 1455 * @param baseUrl Url to resolve relative paths with, if null defaults to 1456 * "about:blank" 1457 * @param data A String of data in the given encoding. 1458 * @param mimeType The MIMEType of the data. i.e. text/html. If null, 1459 * defaults to "text/html" 1460 * @param encoding The encoding of the data. i.e. utf-8, us-ascii 1461 * @param historyUrl URL to use as the history entry. Can be null. 1462 */ 1463 public void loadDataWithBaseURL(String baseUrl, String data, 1464 String mimeType, String encoding, String historyUrl) { 1465 1466 if (baseUrl != null && baseUrl.toLowerCase().startsWith("data:")) { 1467 loadData(data, mimeType, encoding); 1468 return; 1469 } 1470 switchOutDrawHistory(); 1471 WebViewCore.BaseUrlData arg = new WebViewCore.BaseUrlData(); 1472 arg.mBaseUrl = baseUrl; 1473 arg.mData = data; 1474 arg.mMimeType = mimeType; 1475 arg.mEncoding = encoding; 1476 arg.mHistoryUrl = historyUrl; 1477 mWebViewCore.sendMessage(EventHub.LOAD_DATA, arg); 1478 clearTextEntry(false); 1479 } 1480 1481 /** 1482 * Stop the current load. 1483 */ 1484 public void stopLoading() { 1485 // TODO: should we clear all the messages in the queue before sending 1486 // STOP_LOADING? 1487 switchOutDrawHistory(); 1488 mWebViewCore.sendMessage(EventHub.STOP_LOADING); 1489 } 1490 1491 /** 1492 * Reload the current url. 1493 */ 1494 public void reload() { 1495 clearTextEntry(false); 1496 switchOutDrawHistory(); 1497 mWebViewCore.sendMessage(EventHub.RELOAD); 1498 } 1499 1500 /** 1501 * Return true if this WebView has a back history item. 1502 * @return True iff this WebView has a back history item. 1503 */ 1504 public boolean canGoBack() { 1505 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1506 synchronized (l) { 1507 if (l.getClearPending()) { 1508 return false; 1509 } else { 1510 return l.getCurrentIndex() > 0; 1511 } 1512 } 1513 } 1514 1515 /** 1516 * Go back in the history of this WebView. 1517 */ 1518 public void goBack() { 1519 goBackOrForward(-1); 1520 } 1521 1522 /** 1523 * Return true if this WebView has a forward history item. 1524 * @return True iff this Webview has a forward history item. 1525 */ 1526 public boolean canGoForward() { 1527 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1528 synchronized (l) { 1529 if (l.getClearPending()) { 1530 return false; 1531 } else { 1532 return l.getCurrentIndex() < l.getSize() - 1; 1533 } 1534 } 1535 } 1536 1537 /** 1538 * Go forward in the history of this WebView. 1539 */ 1540 public void goForward() { 1541 goBackOrForward(1); 1542 } 1543 1544 /** 1545 * Return true if the page can go back or forward the given 1546 * number of steps. 1547 * @param steps The negative or positive number of steps to move the 1548 * history. 1549 */ 1550 public boolean canGoBackOrForward(int steps) { 1551 WebBackForwardList l = mCallbackProxy.getBackForwardList(); 1552 synchronized (l) { 1553 if (l.getClearPending()) { 1554 return false; 1555 } else { 1556 int newIndex = l.getCurrentIndex() + steps; 1557 return newIndex >= 0 && newIndex < l.getSize(); 1558 } 1559 } 1560 } 1561 1562 /** 1563 * Go to the history item that is the number of steps away from 1564 * the current item. Steps is negative if backward and positive 1565 * if forward. 1566 * @param steps The number of steps to take back or forward in the back 1567 * forward list. 1568 */ 1569 public void goBackOrForward(int steps) { 1570 goBackOrForward(steps, false); 1571 } 1572 1573 private void goBackOrForward(int steps, boolean ignoreSnapshot) { 1574 if (steps != 0) { 1575 clearTextEntry(false); 1576 mWebViewCore.sendMessage(EventHub.GO_BACK_FORWARD, steps, 1577 ignoreSnapshot ? 1 : 0); 1578 } 1579 } 1580 1581 private boolean extendScroll(int y) { 1582 int finalY = mScroller.getFinalY(); 1583 int newY = pinLocY(finalY + y); 1584 if (newY == finalY) return false; 1585 mScroller.setFinalY(newY); 1586 mScroller.extendDuration(computeDuration(0, y)); 1587 return true; 1588 } 1589 1590 /** 1591 * Scroll the contents of the view up by half the view size 1592 * @param top true to jump to the top of the page 1593 * @return true if the page was scrolled 1594 */ 1595 public boolean pageUp(boolean top) { 1596 if (mNativeClass == 0) { 1597 return false; 1598 } 1599 nativeClearCursor(); // start next trackball movement from page edge 1600 if (top) { 1601 // go to the top of the document 1602 return pinScrollTo(mScrollX, 0, true, 0); 1603 } 1604 // Page up 1605 int h = getHeight(); 1606 int y; 1607 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1608 y = -h + PAGE_SCROLL_OVERLAP; 1609 } else { 1610 y = -h / 2; 1611 } 1612 mUserScroll = true; 1613 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 1614 : extendScroll(y); 1615 } 1616 1617 /** 1618 * Scroll the contents of the view down by half the page size 1619 * @param bottom true to jump to bottom of page 1620 * @return true if the page was scrolled 1621 */ 1622 public boolean pageDown(boolean bottom) { 1623 if (mNativeClass == 0) { 1624 return false; 1625 } 1626 nativeClearCursor(); // start next trackball movement from page edge 1627 if (bottom) { 1628 return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0); 1629 } 1630 // Page down. 1631 int h = getHeight(); 1632 int y; 1633 if (h > 2 * PAGE_SCROLL_OVERLAP) { 1634 y = h - PAGE_SCROLL_OVERLAP; 1635 } else { 1636 y = h / 2; 1637 } 1638 mUserScroll = true; 1639 return mScroller.isFinished() ? pinScrollBy(0, y, true, 0) 1640 : extendScroll(y); 1641 } 1642 1643 /** 1644 * Clear the view so that onDraw() will draw nothing but white background, 1645 * and onMeasure() will return 0 if MeasureSpec is not MeasureSpec.EXACTLY 1646 */ 1647 public void clearView() { 1648 mContentWidth = 0; 1649 mContentHeight = 0; 1650 mWebViewCore.sendMessage(EventHub.CLEAR_CONTENT); 1651 } 1652 1653 /** 1654 * Return a new picture that captures the current display of the webview. 1655 * This is a copy of the display, and will be unaffected if the webview 1656 * later loads a different URL. 1657 * 1658 * @return a picture containing the current contents of the view. Note this 1659 * picture is of the entire document, and is not restricted to the 1660 * bounds of the view. 1661 */ 1662 public Picture capturePicture() { 1663 if (null == mWebViewCore) return null; // check for out of memory tab 1664 return mWebViewCore.copyContentPicture(); 1665 } 1666 1667 /** 1668 * Return true if the browser is displaying a TextView for text input. 1669 */ 1670 private boolean inEditingMode() { 1671 return mWebTextView != null && mWebTextView.getParent() != null; 1672 } 1673 1674 /** 1675 * Remove the WebTextView. 1676 * @param disableFocusController If true, send a message to webkit 1677 * disabling the focus controller, so the caret stops blinking. 1678 */ 1679 private void clearTextEntry(boolean disableFocusController) { 1680 if (inEditingMode()) { 1681 mWebTextView.remove(); 1682 if (disableFocusController) { 1683 setFocusControllerInactive(); 1684 } 1685 } 1686 } 1687 1688 /** 1689 * Return the current scale of the WebView 1690 * @return The current scale. 1691 */ 1692 public float getScale() { 1693 return mActualScale; 1694 } 1695 1696 /** 1697 * Set the initial scale for the WebView. 0 means default. If 1698 * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the 1699 * way. Otherwise it starts with 100%. If initial scale is greater than 0, 1700 * WebView starts will this value as initial scale. 1701 * 1702 * @param scaleInPercent The initial scale in percent. 1703 */ 1704 public void setInitialScale(int scaleInPercent) { 1705 mInitialScaleInPercent = scaleInPercent; 1706 } 1707 1708 /** 1709 * Invoke the graphical zoom picker widget for this WebView. This will 1710 * result in the zoom widget appearing on the screen to control the zoom 1711 * level of this WebView. 1712 */ 1713 public void invokeZoomPicker() { 1714 if (!getSettings().supportZoom()) { 1715 Log.w(LOGTAG, "This WebView doesn't support zoom."); 1716 return; 1717 } 1718 clearTextEntry(false); 1719 mZoomManager.invokeZoomPicker(); 1720 } 1721 1722 /** 1723 * Return a HitTestResult based on the current cursor node. If a HTML::a tag 1724 * is found and the anchor has a non-javascript url, the HitTestResult type 1725 * is set to SRC_ANCHOR_TYPE and the url is set in the "extra" field. If the 1726 * anchor does not have a url or if it is a javascript url, the type will 1727 * be UNKNOWN_TYPE and the url has to be retrieved through 1728 * {@link #requestFocusNodeHref} asynchronously. If a HTML::img tag is 1729 * found, the HitTestResult type is set to IMAGE_TYPE and the url is set in 1730 * the "extra" field. A type of 1731 * SRC_IMAGE_ANCHOR_TYPE indicates an anchor with a url that has an image as 1732 * a child node. If a phone number is found, the HitTestResult type is set 1733 * to PHONE_TYPE and the phone number is set in the "extra" field of 1734 * HitTestResult. If a map address is found, the HitTestResult type is set 1735 * to GEO_TYPE and the address is set in the "extra" field of HitTestResult. 1736 * If an email address is found, the HitTestResult type is set to EMAIL_TYPE 1737 * and the email is set in the "extra" field of HitTestResult. Otherwise, 1738 * HitTestResult type is set to UNKNOWN_TYPE. 1739 */ 1740 public HitTestResult getHitTestResult() { 1741 if (mNativeClass == 0) { 1742 return null; 1743 } 1744 1745 HitTestResult result = new HitTestResult(); 1746 if (nativeHasCursorNode()) { 1747 if (nativeCursorIsTextInput()) { 1748 result.setType(HitTestResult.EDIT_TEXT_TYPE); 1749 } else { 1750 String text = nativeCursorText(); 1751 if (text != null) { 1752 if (text.startsWith(SCHEME_TEL)) { 1753 result.setType(HitTestResult.PHONE_TYPE); 1754 result.setExtra(text.substring(SCHEME_TEL.length())); 1755 } else if (text.startsWith(SCHEME_MAILTO)) { 1756 result.setType(HitTestResult.EMAIL_TYPE); 1757 result.setExtra(text.substring(SCHEME_MAILTO.length())); 1758 } else if (text.startsWith(SCHEME_GEO)) { 1759 result.setType(HitTestResult.GEO_TYPE); 1760 result.setExtra(URLDecoder.decode(text 1761 .substring(SCHEME_GEO.length()))); 1762 } else if (nativeCursorIsAnchor()) { 1763 result.setType(HitTestResult.SRC_ANCHOR_TYPE); 1764 result.setExtra(text); 1765 } 1766 } 1767 } 1768 } 1769 int type = result.getType(); 1770 if (type == HitTestResult.UNKNOWN_TYPE 1771 || type == HitTestResult.SRC_ANCHOR_TYPE) { 1772 // Now check to see if it is an image. 1773 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 1774 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 1775 String text = nativeImageURI(contentX, contentY); 1776 if (text != null) { 1777 result.setType(type == HitTestResult.UNKNOWN_TYPE ? 1778 HitTestResult.IMAGE_TYPE : 1779 HitTestResult.SRC_IMAGE_ANCHOR_TYPE); 1780 result.setExtra(text); 1781 } 1782 } 1783 return result; 1784 } 1785 1786 // Called by JNI when the DOM has changed the focus. Clear the focus so 1787 // that new keys will go to the newly focused field 1788 private void domChangedFocus() { 1789 if (inEditingMode()) { 1790 mPrivateHandler.obtainMessage(DOM_FOCUS_CHANGED).sendToTarget(); 1791 } 1792 } 1793 /** 1794 * Request the href of an anchor element due to getFocusNodePath returning 1795 * "href." If hrefMsg is null, this method returns immediately and does not 1796 * dispatch hrefMsg to its target. 1797 * 1798 * @param hrefMsg This message will be dispatched with the result of the 1799 * request as the data member with "url" as key. The result can 1800 * be null. 1801 */ 1802 // FIXME: API change required to change the name of this function. We now 1803 // look at the cursor node, and not the focus node. Also, what is 1804 // getFocusNodePath? 1805 public void requestFocusNodeHref(Message hrefMsg) { 1806 if (hrefMsg == null || mNativeClass == 0) { 1807 return; 1808 } 1809 if (nativeCursorIsAnchor()) { 1810 mWebViewCore.sendMessage(EventHub.REQUEST_CURSOR_HREF, 1811 nativeCursorFramePointer(), nativeCursorNodePointer(), 1812 hrefMsg); 1813 } 1814 } 1815 1816 /** 1817 * Request the url of the image last touched by the user. msg will be sent 1818 * to its target with a String representing the url as its object. 1819 * 1820 * @param msg This message will be dispatched with the result of the request 1821 * as the data member with "url" as key. The result can be null. 1822 */ 1823 public void requestImageRef(Message msg) { 1824 if (0 == mNativeClass) return; // client isn't initialized 1825 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 1826 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 1827 String ref = nativeImageURI(contentX, contentY); 1828 Bundle data = msg.getData(); 1829 data.putString("url", ref); 1830 msg.setData(data); 1831 msg.sendToTarget(); 1832 } 1833 1834 private static int pinLoc(int x, int viewMax, int docMax) { 1835// Log.d(LOGTAG, "-- pinLoc " + x + " " + viewMax + " " + docMax); 1836 if (docMax < viewMax) { // the doc has room on the sides for "blank" 1837 // pin the short document to the top/left of the screen 1838 x = 0; 1839// Log.d(LOGTAG, "--- center " + x); 1840 } else if (x < 0) { 1841 x = 0; 1842// Log.d(LOGTAG, "--- zero"); 1843 } else if (x + viewMax > docMax) { 1844 x = docMax - viewMax; 1845// Log.d(LOGTAG, "--- pin " + x); 1846 } 1847 return x; 1848 } 1849 1850 // Expects x in view coordinates 1851 private int pinLocX(int x) { 1852 return pinLoc(x, getViewWidth(), computeHorizontalScrollRange()); 1853 } 1854 1855 // Expects y in view coordinates 1856 private int pinLocY(int y) { 1857 return pinLoc(y, getViewHeightWithTitle(), 1858 computeVerticalScrollRange() + getTitleHeight()); 1859 } 1860 1861 /** 1862 * A title bar which is embedded in this WebView, and scrolls along with it 1863 * vertically, but not horizontally. 1864 */ 1865 private View mTitleBar; 1866 1867 /** 1868 * Since we draw the title bar ourselves, we removed the shadow from the 1869 * browser's activity. We do want a shadow at the bottom of the title bar, 1870 * or at the top of the screen if the title bar is not visible. This 1871 * drawable serves that purpose. 1872 */ 1873 private Drawable mTitleShadow; 1874 1875 /** 1876 * Add or remove a title bar to be embedded into the WebView, and scroll 1877 * along with it vertically, while remaining in view horizontally. Pass 1878 * null to remove the title bar from the WebView, and return to drawing 1879 * the WebView normally without translating to account for the title bar. 1880 * @hide 1881 */ 1882 public void setEmbeddedTitleBar(View v) { 1883 if (mTitleBar == v) return; 1884 if (mTitleBar != null) { 1885 removeView(mTitleBar); 1886 } 1887 if (null != v) { 1888 addView(v, new AbsoluteLayout.LayoutParams( 1889 ViewGroup.LayoutParams.MATCH_PARENT, 1890 ViewGroup.LayoutParams.WRAP_CONTENT, 0, 0)); 1891 if (mTitleShadow == null) { 1892 mTitleShadow = (Drawable) mContext.getResources().getDrawable( 1893 com.android.internal.R.drawable.title_bar_shadow); 1894 } 1895 } 1896 mTitleBar = v; 1897 } 1898 1899 /** 1900 * Given a distance in view space, convert it to content space. Note: this 1901 * does not reflect translation, just scaling, so this should not be called 1902 * with coordinates, but should be called for dimensions like width or 1903 * height. 1904 */ 1905 private int viewToContentDimension(int d) { 1906 return Math.round(d * mInvActualScale); 1907 } 1908 1909 /** 1910 * Given an x coordinate in view space, convert it to content space. Also 1911 * may be used for absolute heights (such as for the WebTextView's 1912 * textSize, which is unaffected by the height of the title bar). 1913 */ 1914 /*package*/ int viewToContentX(int x) { 1915 return viewToContentDimension(x); 1916 } 1917 1918 /** 1919 * Given a y coordinate in view space, convert it to content space. 1920 * Takes into account the height of the title bar if there is one 1921 * embedded into the WebView. 1922 */ 1923 /*package*/ int viewToContentY(int y) { 1924 return viewToContentDimension(y - getTitleHeight()); 1925 } 1926 1927 /** 1928 * Given a x coordinate in view space, convert it to content space. 1929 * Returns the result as a float. 1930 */ 1931 private float viewToContentXf(int x) { 1932 return x * mInvActualScale; 1933 } 1934 1935 /** 1936 * Given a y coordinate in view space, convert it to content space. 1937 * Takes into account the height of the title bar if there is one 1938 * embedded into the WebView. Returns the result as a float. 1939 */ 1940 private float viewToContentYf(int y) { 1941 return (y - getTitleHeight()) * mInvActualScale; 1942 } 1943 1944 /** 1945 * Given a distance in content space, convert it to view space. Note: this 1946 * does not reflect translation, just scaling, so this should not be called 1947 * with coordinates, but should be called for dimensions like width or 1948 * height. 1949 */ 1950 /*package*/ int contentToViewDimension(int d) { 1951 return Math.round(d * mActualScale); 1952 } 1953 1954 /** 1955 * Given an x coordinate in content space, convert it to view 1956 * space. 1957 */ 1958 /*package*/ int contentToViewX(int x) { 1959 return contentToViewDimension(x); 1960 } 1961 1962 /** 1963 * Given a y coordinate in content space, convert it to view 1964 * space. Takes into account the height of the title bar. 1965 */ 1966 /*package*/ int contentToViewY(int y) { 1967 return contentToViewDimension(y) + getTitleHeight(); 1968 } 1969 1970 private Rect contentToViewRect(Rect x) { 1971 return new Rect(contentToViewX(x.left), contentToViewY(x.top), 1972 contentToViewX(x.right), contentToViewY(x.bottom)); 1973 } 1974 1975 /* To invalidate a rectangle in content coordinates, we need to transform 1976 the rect into view coordinates, so we can then call invalidate(...). 1977 1978 Normally, we would just call contentToView[XY](...), which eventually 1979 calls Math.round(coordinate * mActualScale). However, for invalidates, 1980 we need to account for the slop that occurs with antialiasing. To 1981 address that, we are a little more liberal in the size of the rect that 1982 we invalidate. 1983 1984 This liberal calculation calls floor() for the top/left, and ceil() for 1985 the bottom/right coordinates. This catches the possible extra pixels of 1986 antialiasing that we might have missed with just round(). 1987 */ 1988 1989 // Called by JNI to invalidate the View, given rectangle coordinates in 1990 // content space 1991 private void viewInvalidate(int l, int t, int r, int b) { 1992 final float scale = mActualScale; 1993 final int dy = getTitleHeight(); 1994 invalidate((int)Math.floor(l * scale), 1995 (int)Math.floor(t * scale) + dy, 1996 (int)Math.ceil(r * scale), 1997 (int)Math.ceil(b * scale) + dy); 1998 } 1999 2000 // Called by JNI to invalidate the View after a delay, given rectangle 2001 // coordinates in content space 2002 private void viewInvalidateDelayed(long delay, int l, int t, int r, int b) { 2003 final float scale = mActualScale; 2004 final int dy = getTitleHeight(); 2005 postInvalidateDelayed(delay, 2006 (int)Math.floor(l * scale), 2007 (int)Math.floor(t * scale) + dy, 2008 (int)Math.ceil(r * scale), 2009 (int)Math.ceil(b * scale) + dy); 2010 } 2011 2012 private void invalidateContentRect(Rect r) { 2013 viewInvalidate(r.left, r.top, r.right, r.bottom); 2014 } 2015 2016 // stop the scroll animation, and don't let a subsequent fling add 2017 // to the existing velocity 2018 private void abortAnimation() { 2019 mScroller.abortAnimation(); 2020 mLastVelocity = 0; 2021 } 2022 2023 /* call from webcoreview.draw(), so we're still executing in the UI thread 2024 */ 2025 private void recordNewContentSize(int w, int h, boolean updateLayout) { 2026 2027 // premature data from webkit, ignore 2028 if ((w | h) == 0) { 2029 return; 2030 } 2031 2032 // don't abort a scroll animation if we didn't change anything 2033 if (mContentWidth != w || mContentHeight != h) { 2034 // record new dimensions 2035 mContentWidth = w; 2036 mContentHeight = h; 2037 // If history Picture is drawn, don't update scroll. They will be 2038 // updated when we get out of that mode. 2039 if (!mDrawHistory) { 2040 // repin our scroll, taking into account the new content size 2041 int oldX = mScrollX; 2042 int oldY = mScrollY; 2043 mScrollX = pinLocX(mScrollX); 2044 mScrollY = pinLocY(mScrollY); 2045 if (oldX != mScrollX || oldY != mScrollY) { 2046 onScrollChanged(mScrollX, mScrollY, oldX, oldY); 2047 } 2048 if (!mScroller.isFinished()) { 2049 // We are in the middle of a scroll. Repin the final scroll 2050 // position. 2051 mScroller.setFinalX(pinLocX(mScroller.getFinalX())); 2052 mScroller.setFinalY(pinLocY(mScroller.getFinalY())); 2053 } 2054 } 2055 } 2056 contentSizeChanged(updateLayout); 2057 } 2058 2059 private void setNewZoomScale(float scale, boolean updateTextWrapScale, 2060 boolean force) { 2061 if (scale < mZoomManager.mMinZoomScale) { 2062 scale = mZoomManager.mMinZoomScale; 2063 // set mInZoomOverview for non mobile sites 2064 if (scale < mDefaultScale) { 2065 mZoomManager.mInZoomOverview = true; 2066 } 2067 } else if (scale > mZoomManager.mMaxZoomScale) { 2068 scale = mZoomManager.mMaxZoomScale; 2069 } 2070 if (updateTextWrapScale) { 2071 mTextWrapScale = scale; 2072 // reset mLastHeightSent to force VIEW_SIZE_CHANGED sent to WebKit 2073 mLastHeightSent = 0; 2074 } 2075 if (scale != mActualScale || force) { 2076 if (mDrawHistory) { 2077 // If history Picture is drawn, don't update scroll. They will 2078 // be updated when we get out of that mode. 2079 if (scale != mActualScale && !mPreviewZoomOnly) { 2080 mCallbackProxy.onScaleChanged(mActualScale, scale); 2081 } 2082 mActualScale = scale; 2083 mInvActualScale = 1 / scale; 2084 sendViewSizeZoom(); 2085 } else { 2086 // update our scroll so we don't appear to jump 2087 // i.e. keep the center of the doc in the center of the view 2088 2089 int oldX = mScrollX; 2090 int oldY = mScrollY; 2091 float ratio = scale * mInvActualScale; // old inverse 2092 float sx = ratio * oldX + (ratio - 1) * mZoomCenterX; 2093 float sy = ratio * oldY + (ratio - 1) 2094 * (mZoomCenterY - getTitleHeight()); 2095 2096 // now update our new scale and inverse 2097 if (scale != mActualScale && !mPreviewZoomOnly) { 2098 mCallbackProxy.onScaleChanged(mActualScale, scale); 2099 } 2100 mActualScale = scale; 2101 mInvActualScale = 1 / scale; 2102 2103 // Scale all the child views 2104 mViewManager.scaleAll(); 2105 2106 // as we don't have animation for scaling, don't do animation 2107 // for scrolling, as it causes weird intermediate state 2108 // pinScrollTo(Math.round(sx), Math.round(sy)); 2109 mScrollX = pinLocX(Math.round(sx)); 2110 mScrollY = pinLocY(Math.round(sy)); 2111 2112 // update webkit 2113 if (oldX != mScrollX || oldY != mScrollY) { 2114 onScrollChanged(mScrollX, mScrollY, oldX, oldY); 2115 } else { 2116 // the scroll position is adjusted at the beginning of the 2117 // zoom animation. But we want to update the WebKit at the 2118 // end of the zoom animation. See comments in onScaleEnd(). 2119 sendOurVisibleRect(); 2120 } 2121 sendViewSizeZoom(); 2122 } 2123 } 2124 } 2125 2126 // Used to avoid sending many visible rect messages. 2127 private Rect mLastVisibleRectSent; 2128 private Rect mLastGlobalRect; 2129 2130 private Rect sendOurVisibleRect() { 2131 if (mPreviewZoomOnly) return mLastVisibleRectSent; 2132 2133 Rect rect = new Rect(); 2134 calcOurContentVisibleRect(rect); 2135 // Rect.equals() checks for null input. 2136 if (!rect.equals(mLastVisibleRectSent)) { 2137 Point pos = new Point(rect.left, rect.top); 2138 mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET, 2139 nativeMoveGeneration(), 0, pos); 2140 mLastVisibleRectSent = rect; 2141 } 2142 Rect globalRect = new Rect(); 2143 if (getGlobalVisibleRect(globalRect) 2144 && !globalRect.equals(mLastGlobalRect)) { 2145 if (DebugFlags.WEB_VIEW) { 2146 Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + "," 2147 + globalRect.top + ",r=" + globalRect.right + ",b=" 2148 + globalRect.bottom); 2149 } 2150 // TODO: the global offset is only used by windowRect() 2151 // in ChromeClientAndroid ; other clients such as touch 2152 // and mouse events could return view + screen relative points. 2153 mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect); 2154 mLastGlobalRect = globalRect; 2155 } 2156 return rect; 2157 } 2158 2159 // Sets r to be the visible rectangle of our webview in view coordinates 2160 private void calcOurVisibleRect(Rect r) { 2161 Point p = new Point(); 2162 getGlobalVisibleRect(r, p); 2163 r.offset(-p.x, -p.y); 2164 } 2165 2166 // Sets r to be our visible rectangle in content coordinates 2167 private void calcOurContentVisibleRect(Rect r) { 2168 calcOurVisibleRect(r); 2169 // pin the rect to the bounds of the content 2170 r.left = Math.max(viewToContentX(r.left), 0); 2171 // viewToContentY will remove the total height of the title bar. Add 2172 // the visible height back in to account for the fact that if the title 2173 // bar is partially visible, the part of the visible rect which is 2174 // displaying our content is displaced by that amount. 2175 r.top = Math.max(viewToContentY(r.top + getVisibleTitleHeight()), 0); 2176 r.right = Math.min(viewToContentX(r.right), mContentWidth); 2177 r.bottom = Math.min(viewToContentY(r.bottom), mContentHeight); 2178 } 2179 2180 // Sets r to be our visible rectangle in content coordinates. We use this 2181 // method on the native side to compute the position of the fixed layers. 2182 // Uses floating coordinates (necessary to correctly place elements when 2183 // the scale factor is not 1) 2184 private void calcOurContentVisibleRectF(RectF r) { 2185 Rect ri = new Rect(0,0,0,0); 2186 calcOurVisibleRect(ri); 2187 // pin the rect to the bounds of the content 2188 r.left = Math.max(viewToContentXf(ri.left), 0.0f); 2189 // viewToContentY will remove the total height of the title bar. Add 2190 // the visible height back in to account for the fact that if the title 2191 // bar is partially visible, the part of the visible rect which is 2192 // displaying our content is displaced by that amount. 2193 r.top = Math.max(viewToContentYf(ri.top + getVisibleTitleHeight()), 0.0f); 2194 r.right = Math.min(viewToContentXf(ri.right), (float)mContentWidth); 2195 r.bottom = Math.min(viewToContentYf(ri.bottom), (float)mContentHeight); 2196 } 2197 2198 static class ViewSizeData { 2199 int mWidth; 2200 int mHeight; 2201 int mTextWrapWidth; 2202 int mAnchorX; 2203 int mAnchorY; 2204 float mScale; 2205 boolean mIgnoreHeight; 2206 } 2207 2208 /** 2209 * Compute unzoomed width and height, and if they differ from the last 2210 * values we sent, send them to webkit (to be used has new viewport) 2211 * 2212 * @return true if new values were sent 2213 */ 2214 private boolean sendViewSizeZoom() { 2215 if (mPreviewZoomOnly) return false; 2216 2217 int viewWidth = getViewWidth(); 2218 int newWidth = Math.round(viewWidth * mInvActualScale); 2219 int newHeight = Math.round(getViewHeight() * mInvActualScale); 2220 /* 2221 * Because the native side may have already done a layout before the 2222 * View system was able to measure us, we have to send a height of 0 to 2223 * remove excess whitespace when we grow our width. This will trigger a 2224 * layout and a change in content size. This content size change will 2225 * mean that contentSizeChanged will either call this method directly or 2226 * indirectly from onSizeChanged. 2227 */ 2228 if (newWidth > mLastWidthSent && mWrapContent) { 2229 newHeight = 0; 2230 } 2231 // Avoid sending another message if the dimensions have not changed. 2232 if (newWidth != mLastWidthSent || newHeight != mLastHeightSent) { 2233 ViewSizeData data = new ViewSizeData(); 2234 data.mWidth = newWidth; 2235 data.mHeight = newHeight; 2236 data.mTextWrapWidth = Math.round(viewWidth / mTextWrapScale);; 2237 data.mScale = mActualScale; 2238 data.mIgnoreHeight = mZoomScale != 0 && !mHeightCanMeasure; 2239 data.mAnchorX = mAnchorX; 2240 data.mAnchorY = mAnchorY; 2241 mWebViewCore.sendMessage(EventHub.VIEW_SIZE_CHANGED, data); 2242 mLastWidthSent = newWidth; 2243 mLastHeightSent = newHeight; 2244 mAnchorX = mAnchorY = 0; 2245 return true; 2246 } 2247 return false; 2248 } 2249 2250 @Override 2251 protected int computeHorizontalScrollRange() { 2252 if (mDrawHistory) { 2253 return mHistoryWidth; 2254 } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF 2255 && (mActualScale - mZoomManager.mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) { 2256 // only honor the scrollbar mode when it is at minimum zoom level 2257 return computeHorizontalScrollExtent(); 2258 } else { 2259 // to avoid rounding error caused unnecessary scrollbar, use floor 2260 return (int) Math.floor(mContentWidth * mActualScale); 2261 } 2262 } 2263 2264 @Override 2265 protected int computeVerticalScrollRange() { 2266 if (mDrawHistory) { 2267 return mHistoryHeight; 2268 } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF 2269 && (mActualScale - mZoomManager.mMinZoomScale <= MINIMUM_SCALE_INCREMENT)) { 2270 // only honor the scrollbar mode when it is at minimum zoom level 2271 return computeVerticalScrollExtent(); 2272 } else { 2273 // to avoid rounding error caused unnecessary scrollbar, use floor 2274 return (int) Math.floor(mContentHeight * mActualScale); 2275 } 2276 } 2277 2278 @Override 2279 protected int computeVerticalScrollOffset() { 2280 return Math.max(mScrollY - getTitleHeight(), 0); 2281 } 2282 2283 @Override 2284 protected int computeVerticalScrollExtent() { 2285 return getViewHeight(); 2286 } 2287 2288 /** @hide */ 2289 @Override 2290 protected void onDrawVerticalScrollBar(Canvas canvas, 2291 Drawable scrollBar, 2292 int l, int t, int r, int b) { 2293 scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b); 2294 scrollBar.draw(canvas); 2295 } 2296 2297 /** 2298 * Get the url for the current page. This is not always the same as the url 2299 * passed to WebViewClient.onPageStarted because although the load for 2300 * that url has begun, the current page may not have changed. 2301 * @return The url for the current page. 2302 */ 2303 public String getUrl() { 2304 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2305 return h != null ? h.getUrl() : null; 2306 } 2307 2308 /** 2309 * Get the original url for the current page. This is not always the same 2310 * as the url passed to WebViewClient.onPageStarted because although the 2311 * load for that url has begun, the current page may not have changed. 2312 * Also, there may have been redirects resulting in a different url to that 2313 * originally requested. 2314 * @return The url that was originally requested for the current page. 2315 */ 2316 public String getOriginalUrl() { 2317 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2318 return h != null ? h.getOriginalUrl() : null; 2319 } 2320 2321 /** 2322 * Get the title for the current page. This is the title of the current page 2323 * until WebViewClient.onReceivedTitle is called. 2324 * @return The title for the current page. 2325 */ 2326 public String getTitle() { 2327 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2328 return h != null ? h.getTitle() : null; 2329 } 2330 2331 /** 2332 * Get the favicon for the current page. This is the favicon of the current 2333 * page until WebViewClient.onReceivedIcon is called. 2334 * @return The favicon for the current page. 2335 */ 2336 public Bitmap getFavicon() { 2337 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2338 return h != null ? h.getFavicon() : null; 2339 } 2340 2341 /** 2342 * Get the touch icon url for the apple-touch-icon <link> element. 2343 * @hide 2344 */ 2345 public String getTouchIconUrl() { 2346 WebHistoryItem h = mCallbackProxy.getBackForwardList().getCurrentItem(); 2347 return h != null ? h.getTouchIconUrl() : null; 2348 } 2349 2350 /** 2351 * Get the progress for the current page. 2352 * @return The progress for the current page between 0 and 100. 2353 */ 2354 public int getProgress() { 2355 return mCallbackProxy.getProgress(); 2356 } 2357 2358 /** 2359 * @return the height of the HTML content. 2360 */ 2361 public int getContentHeight() { 2362 return mContentHeight; 2363 } 2364 2365 /** 2366 * @return the width of the HTML content. 2367 * @hide 2368 */ 2369 public int getContentWidth() { 2370 return mContentWidth; 2371 } 2372 2373 /** 2374 * Pause all layout, parsing, and javascript timers for all webviews. This 2375 * is a global requests, not restricted to just this webview. This can be 2376 * useful if the application has been paused. 2377 */ 2378 public void pauseTimers() { 2379 mWebViewCore.sendMessage(EventHub.PAUSE_TIMERS); 2380 } 2381 2382 /** 2383 * Resume all layout, parsing, and javascript timers for all webviews. 2384 * This will resume dispatching all timers. 2385 */ 2386 public void resumeTimers() { 2387 mWebViewCore.sendMessage(EventHub.RESUME_TIMERS); 2388 } 2389 2390 /** 2391 * Call this to pause any extra processing associated with this view and 2392 * its associated DOM/plugins/javascript/etc. For example, if the view is 2393 * taken offscreen, this could be called to reduce unnecessary CPU and/or 2394 * network traffic. When the view is again "active", call onResume(). 2395 * 2396 * Note that this differs from pauseTimers(), which affects all views/DOMs 2397 * @hide 2398 */ 2399 public void onPause() { 2400 if (!mIsPaused) { 2401 mIsPaused = true; 2402 mWebViewCore.sendMessage(EventHub.ON_PAUSE); 2403 } 2404 } 2405 2406 /** 2407 * Call this to balanace a previous call to onPause() 2408 * @hide 2409 */ 2410 public void onResume() { 2411 if (mIsPaused) { 2412 mIsPaused = false; 2413 mWebViewCore.sendMessage(EventHub.ON_RESUME); 2414 } 2415 } 2416 2417 /** 2418 * Returns true if the view is paused, meaning onPause() was called. Calling 2419 * onResume() sets the paused state back to false. 2420 * @hide 2421 */ 2422 public boolean isPaused() { 2423 return mIsPaused; 2424 } 2425 2426 /** 2427 * Call this to inform the view that memory is low so that it can 2428 * free any available memory. 2429 */ 2430 public void freeMemory() { 2431 mWebViewCore.sendMessage(EventHub.FREE_MEMORY); 2432 } 2433 2434 /** 2435 * Clear the resource cache. Note that the cache is per-application, so 2436 * this will clear the cache for all WebViews used. 2437 * 2438 * @param includeDiskFiles If false, only the RAM cache is cleared. 2439 */ 2440 public void clearCache(boolean includeDiskFiles) { 2441 // Note: this really needs to be a static method as it clears cache for all 2442 // WebView. But we need mWebViewCore to send message to WebCore thread, so 2443 // we can't make this static. 2444 mWebViewCore.sendMessage(EventHub.CLEAR_CACHE, 2445 includeDiskFiles ? 1 : 0, 0); 2446 } 2447 2448 /** 2449 * Make sure that clearing the form data removes the adapter from the 2450 * currently focused textfield if there is one. 2451 */ 2452 public void clearFormData() { 2453 if (inEditingMode()) { 2454 AutoCompleteAdapter adapter = null; 2455 mWebTextView.setAdapterCustom(adapter); 2456 } 2457 } 2458 2459 /** 2460 * Tell the WebView to clear its internal back/forward list. 2461 */ 2462 public void clearHistory() { 2463 mCallbackProxy.getBackForwardList().setClearPending(); 2464 mWebViewCore.sendMessage(EventHub.CLEAR_HISTORY); 2465 } 2466 2467 /** 2468 * Clear the SSL preferences table stored in response to proceeding with SSL 2469 * certificate errors. 2470 */ 2471 public void clearSslPreferences() { 2472 mWebViewCore.sendMessage(EventHub.CLEAR_SSL_PREF_TABLE); 2473 } 2474 2475 /** 2476 * Return the WebBackForwardList for this WebView. This contains the 2477 * back/forward list for use in querying each item in the history stack. 2478 * This is a copy of the private WebBackForwardList so it contains only a 2479 * snapshot of the current state. Multiple calls to this method may return 2480 * different objects. The object returned from this method will not be 2481 * updated to reflect any new state. 2482 */ 2483 public WebBackForwardList copyBackForwardList() { 2484 return mCallbackProxy.getBackForwardList().clone(); 2485 } 2486 2487 /* 2488 * Highlight and scroll to the next occurance of String in findAll. 2489 * Wraps the page infinitely, and scrolls. Must be called after 2490 * calling findAll. 2491 * 2492 * @param forward Direction to search. 2493 */ 2494 public void findNext(boolean forward) { 2495 if (0 == mNativeClass) return; // client isn't initialized 2496 nativeFindNext(forward); 2497 } 2498 2499 /* 2500 * Find all instances of find on the page and highlight them. 2501 * @param find String to find. 2502 * @return int The number of occurances of the String "find" 2503 * that were found. 2504 */ 2505 public int findAll(String find) { 2506 if (0 == mNativeClass) return 0; // client isn't initialized 2507 int result = find != null ? nativeFindAll(find.toLowerCase(), 2508 find.toUpperCase()) : 0; 2509 invalidate(); 2510 mLastFind = find; 2511 return result; 2512 } 2513 2514 /** 2515 * @hide 2516 */ 2517 public void setFindIsUp(boolean isUp) { 2518 mFindIsUp = isUp; 2519 if (0 == mNativeClass) return; // client isn't initialized 2520 nativeSetFindIsUp(isUp); 2521 } 2522 2523 /** 2524 * @hide 2525 */ 2526 public int findIndex() { 2527 if (0 == mNativeClass) return -1; 2528 return nativeFindIndex(); 2529 } 2530 2531 /** 2532 * @hide 2533 */ 2534 public boolean getFindIsUp() { return mFindIsUp; } 2535 2536 // Used to know whether the find dialog is open. Affects whether 2537 // or not we draw the highlights for matches. 2538 private boolean mFindIsUp; 2539 2540 // Keep track of the last string sent, so we can search again after an 2541 // orientation change or the dismissal of the soft keyboard. 2542 private String mLastFind; 2543 2544 /** 2545 * Return the first substring consisting of the address of a physical 2546 * location. Currently, only addresses in the United States are detected, 2547 * and consist of: 2548 * - a house number 2549 * - a street name 2550 * - a street type (Road, Circle, etc), either spelled out or abbreviated 2551 * - a city name 2552 * - a state or territory, either spelled out or two-letter abbr. 2553 * - an optional 5 digit or 9 digit zip code. 2554 * 2555 * All names must be correctly capitalized, and the zip code, if present, 2556 * must be valid for the state. The street type must be a standard USPS 2557 * spelling or abbreviation. The state or territory must also be spelled 2558 * or abbreviated using USPS standards. The house number may not exceed 2559 * five digits. 2560 * @param addr The string to search for addresses. 2561 * 2562 * @return the address, or if no address is found, return null. 2563 */ 2564 public static String findAddress(String addr) { 2565 return findAddress(addr, false); 2566 } 2567 2568 /** 2569 * @hide 2570 * Return the first substring consisting of the address of a physical 2571 * location. Currently, only addresses in the United States are detected, 2572 * and consist of: 2573 * - a house number 2574 * - a street name 2575 * - a street type (Road, Circle, etc), either spelled out or abbreviated 2576 * - a city name 2577 * - a state or territory, either spelled out or two-letter abbr. 2578 * - an optional 5 digit or 9 digit zip code. 2579 * 2580 * Names are optionally capitalized, and the zip code, if present, 2581 * must be valid for the state. The street type must be a standard USPS 2582 * spelling or abbreviation. The state or territory must also be spelled 2583 * or abbreviated using USPS standards. The house number may not exceed 2584 * five digits. 2585 * @param addr The string to search for addresses. 2586 * @param caseInsensitive addr Set to true to make search ignore case. 2587 * 2588 * @return the address, or if no address is found, return null. 2589 */ 2590 public static String findAddress(String addr, boolean caseInsensitive) { 2591 return WebViewCore.nativeFindAddress(addr, caseInsensitive); 2592 } 2593 2594 /* 2595 * Clear the highlighting surrounding text matches created by findAll. 2596 */ 2597 public void clearMatches() { 2598 mLastFind = ""; 2599 if (mNativeClass == 0) 2600 return; 2601 nativeSetFindIsEmpty(); 2602 invalidate(); 2603 } 2604 2605 /** 2606 * @hide 2607 */ 2608 public void notifyFindDialogDismissed() { 2609 if (mWebViewCore == null) { 2610 return; 2611 } 2612 clearMatches(); 2613 setFindIsUp(false); 2614 // Now that the dialog has been removed, ensure that we scroll to a 2615 // location that is not beyond the end of the page. 2616 pinScrollTo(mScrollX, mScrollY, false, 0); 2617 invalidate(); 2618 } 2619 2620 /** 2621 * Query the document to see if it contains any image references. The 2622 * message object will be dispatched with arg1 being set to 1 if images 2623 * were found and 0 if the document does not reference any images. 2624 * @param response The message that will be dispatched with the result. 2625 */ 2626 public void documentHasImages(Message response) { 2627 if (response == null) { 2628 return; 2629 } 2630 mWebViewCore.sendMessage(EventHub.DOC_HAS_IMAGES, response); 2631 } 2632 2633 @Override 2634 public void computeScroll() { 2635 if (mScroller.computeScrollOffset()) { 2636 int oldX = mScrollX; 2637 int oldY = mScrollY; 2638 mScrollX = mScroller.getCurrX(); 2639 mScrollY = mScroller.getCurrY(); 2640 postInvalidate(); // So we draw again 2641 if (oldX != mScrollX || oldY != mScrollY) { 2642 onScrollChanged(mScrollX, mScrollY, oldX, oldY); 2643 } 2644 } else { 2645 super.computeScroll(); 2646 } 2647 } 2648 2649 private static int computeDuration(int dx, int dy) { 2650 int distance = Math.max(Math.abs(dx), Math.abs(dy)); 2651 int duration = distance * 1000 / STD_SPEED; 2652 return Math.min(duration, MAX_DURATION); 2653 } 2654 2655 // helper to pin the scrollBy parameters (already in view coordinates) 2656 // returns true if the scroll was changed 2657 private boolean pinScrollBy(int dx, int dy, boolean animate, int animationDuration) { 2658 return pinScrollTo(mScrollX + dx, mScrollY + dy, animate, animationDuration); 2659 } 2660 // helper to pin the scrollTo parameters (already in view coordinates) 2661 // returns true if the scroll was changed 2662 private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) { 2663 x = pinLocX(x); 2664 y = pinLocY(y); 2665 int dx = x - mScrollX; 2666 int dy = y - mScrollY; 2667 2668 if ((dx | dy) == 0) { 2669 return false; 2670 } 2671 if (animate) { 2672 // Log.d(LOGTAG, "startScroll: " + dx + " " + dy); 2673 mScroller.startScroll(mScrollX, mScrollY, dx, dy, 2674 animationDuration > 0 ? animationDuration : computeDuration(dx, dy)); 2675 awakenScrollBars(mScroller.getDuration()); 2676 invalidate(); 2677 } else { 2678 abortAnimation(); // just in case 2679 scrollTo(x, y); 2680 } 2681 return true; 2682 } 2683 2684 // Scale from content to view coordinates, and pin. 2685 // Also called by jni webview.cpp 2686 private boolean setContentScrollBy(int cx, int cy, boolean animate) { 2687 if (mDrawHistory) { 2688 // disallow WebView to change the scroll position as History Picture 2689 // is used in the view system. 2690 // TODO: as we switchOutDrawHistory when trackball or navigation 2691 // keys are hit, this should be safe. Right? 2692 return false; 2693 } 2694 cx = contentToViewDimension(cx); 2695 cy = contentToViewDimension(cy); 2696 if (mHeightCanMeasure) { 2697 // move our visible rect according to scroll request 2698 if (cy != 0) { 2699 Rect tempRect = new Rect(); 2700 calcOurVisibleRect(tempRect); 2701 tempRect.offset(cx, cy); 2702 requestRectangleOnScreen(tempRect); 2703 } 2704 // FIXME: We scroll horizontally no matter what because currently 2705 // ScrollView and ListView will not scroll horizontally. 2706 // FIXME: Why do we only scroll horizontally if there is no 2707 // vertical scroll? 2708// Log.d(LOGTAG, "setContentScrollBy cy=" + cy); 2709 return cy == 0 && cx != 0 && pinScrollBy(cx, 0, animate, 0); 2710 } else { 2711 return pinScrollBy(cx, cy, animate, 0); 2712 } 2713 } 2714 2715 /** 2716 * Called by CallbackProxy when the page finishes loading. 2717 * @param url The URL of the page which has finished loading. 2718 */ 2719 /* package */ void onPageFinished(String url) { 2720 if (mPageThatNeedsToSlideTitleBarOffScreen != null) { 2721 // If the user is now on a different page, or has scrolled the page 2722 // past the point where the title bar is offscreen, ignore the 2723 // scroll request. 2724 if (mPageThatNeedsToSlideTitleBarOffScreen.equals(url) 2725 && mScrollX == 0 && mScrollY == 0) { 2726 pinScrollTo(0, mYDistanceToSlideTitleOffScreen, true, 2727 SLIDE_TITLE_DURATION); 2728 } 2729 mPageThatNeedsToSlideTitleBarOffScreen = null; 2730 } 2731 2732 injectAccessibilityForUrl(url); 2733 } 2734 2735 /** 2736 * This method injects accessibility in the loaded document if accessibility 2737 * is enabled. If JavaScript is enabled we try to inject a URL specific script. 2738 * If no URL specific script is found or JavaScript is disabled we fallback to 2739 * the default {@link AccessibilityInjector} implementation. 2740 * 2741 * @param url The URL loaded by this {@link WebView}. 2742 */ 2743 private void injectAccessibilityForUrl(String url) { 2744 if (AccessibilityManager.getInstance(mContext).isEnabled()) { 2745 if (getSettings().getJavaScriptEnabled()) { 2746 loadUrl(ACCESSIBILITY_SCRIPT_CHOOSER_JAVASCRIPT); 2747 } else if (mAccessibilityInjector == null) { 2748 mAccessibilityInjector = new AccessibilityInjector(this); 2749 } 2750 } else { 2751 // it is possible that accessibility was turned off between reloads 2752 mAccessibilityInjector = null; 2753 } 2754 } 2755 2756 /** 2757 * The URL of a page that sent a message to scroll the title bar off screen. 2758 * 2759 * Many mobile sites tell the page to scroll to (0,1) in order to scroll the 2760 * title bar off the screen. Sometimes, the scroll position is set before 2761 * the page finishes loading. Rather than scrolling while the page is still 2762 * loading, keep track of the URL and new scroll position so we can perform 2763 * the scroll once the page finishes loading. 2764 */ 2765 private String mPageThatNeedsToSlideTitleBarOffScreen; 2766 2767 /** 2768 * The destination Y scroll position to be used when the page finishes 2769 * loading. See mPageThatNeedsToSlideTitleBarOffScreen. 2770 */ 2771 private int mYDistanceToSlideTitleOffScreen; 2772 2773 // scale from content to view coordinates, and pin 2774 // return true if pin caused the final x/y different than the request cx/cy, 2775 // and a future scroll may reach the request cx/cy after our size has 2776 // changed 2777 // return false if the view scroll to the exact position as it is requested, 2778 // where negative numbers are taken to mean 0 2779 private boolean setContentScrollTo(int cx, int cy) { 2780 if (mDrawHistory) { 2781 // disallow WebView to change the scroll position as History Picture 2782 // is used in the view system. 2783 // One known case where this is called is that WebCore tries to 2784 // restore the scroll position. As history Picture already uses the 2785 // saved scroll position, it is ok to skip this. 2786 return false; 2787 } 2788 int vx; 2789 int vy; 2790 if ((cx | cy) == 0) { 2791 // If the page is being scrolled to (0,0), do not add in the title 2792 // bar's height, and simply scroll to (0,0). (The only other work 2793 // in contentToView_ is to multiply, so this would not change 0.) 2794 vx = 0; 2795 vy = 0; 2796 } else { 2797 vx = contentToViewX(cx); 2798 vy = contentToViewY(cy); 2799 } 2800// Log.d(LOGTAG, "content scrollTo [" + cx + " " + cy + "] view=[" + 2801// vx + " " + vy + "]"); 2802 // Some mobile sites attempt to scroll the title bar off the page by 2803 // scrolling to (0,1). If we are at the top left corner of the 2804 // page, assume this is an attempt to scroll off the title bar, and 2805 // animate the title bar off screen slowly enough that the user can see 2806 // it. 2807 if (cx == 0 && cy == 1 && mScrollX == 0 && mScrollY == 0 2808 && mTitleBar != null) { 2809 // FIXME: 100 should be defined somewhere as our max progress. 2810 if (getProgress() < 100) { 2811 // Wait to scroll the title bar off screen until the page has 2812 // finished loading. Keep track of the URL and the destination 2813 // Y position 2814 mPageThatNeedsToSlideTitleBarOffScreen = getUrl(); 2815 mYDistanceToSlideTitleOffScreen = vy; 2816 } else { 2817 pinScrollTo(vx, vy, true, SLIDE_TITLE_DURATION); 2818 } 2819 // Since we are animating, we have not yet reached the desired 2820 // scroll position. Do not return true to request another attempt 2821 return false; 2822 } 2823 pinScrollTo(vx, vy, false, 0); 2824 // If the request was to scroll to a negative coordinate, treat it as if 2825 // it was a request to scroll to 0 2826 if ((mScrollX != vx && cx >= 0) || (mScrollY != vy && cy >= 0)) { 2827 return true; 2828 } else { 2829 return false; 2830 } 2831 } 2832 2833 // scale from content to view coordinates, and pin 2834 private void spawnContentScrollTo(int cx, int cy) { 2835 if (mDrawHistory) { 2836 // disallow WebView to change the scroll position as History Picture 2837 // is used in the view system. 2838 return; 2839 } 2840 int vx = contentToViewX(cx); 2841 int vy = contentToViewY(cy); 2842 pinScrollTo(vx, vy, true, 0); 2843 } 2844 2845 /** 2846 * These are from webkit, and are in content coordinate system (unzoomed) 2847 */ 2848 private void contentSizeChanged(boolean updateLayout) { 2849 // suppress 0,0 since we usually see real dimensions soon after 2850 // this avoids drawing the prev content in a funny place. If we find a 2851 // way to consolidate these notifications, this check may become 2852 // obsolete 2853 if ((mContentWidth | mContentHeight) == 0) { 2854 return; 2855 } 2856 2857 if (mHeightCanMeasure) { 2858 if (getMeasuredHeight() != contentToViewDimension(mContentHeight) 2859 || updateLayout) { 2860 requestLayout(); 2861 } 2862 } else if (mWidthCanMeasure) { 2863 if (getMeasuredWidth() != contentToViewDimension(mContentWidth) 2864 || updateLayout) { 2865 requestLayout(); 2866 } 2867 } else { 2868 // If we don't request a layout, try to send our view size to the 2869 // native side to ensure that WebCore has the correct dimensions. 2870 sendViewSizeZoom(); 2871 } 2872 } 2873 2874 /** 2875 * Set the WebViewClient that will receive various notifications and 2876 * requests. This will replace the current handler. 2877 * @param client An implementation of WebViewClient. 2878 */ 2879 public void setWebViewClient(WebViewClient client) { 2880 mCallbackProxy.setWebViewClient(client); 2881 } 2882 2883 /** 2884 * Gets the WebViewClient 2885 * @return the current WebViewClient instance. 2886 * 2887 *@hide pending API council approval. 2888 */ 2889 public WebViewClient getWebViewClient() { 2890 return mCallbackProxy.getWebViewClient(); 2891 } 2892 2893 /** 2894 * Register the interface to be used when content can not be handled by 2895 * the rendering engine, and should be downloaded instead. This will replace 2896 * the current handler. 2897 * @param listener An implementation of DownloadListener. 2898 */ 2899 public void setDownloadListener(DownloadListener listener) { 2900 mCallbackProxy.setDownloadListener(listener); 2901 } 2902 2903 /** 2904 * Set the chrome handler. This is an implementation of WebChromeClient for 2905 * use in handling Javascript dialogs, favicons, titles, and the progress. 2906 * This will replace the current handler. 2907 * @param client An implementation of WebChromeClient. 2908 */ 2909 public void setWebChromeClient(WebChromeClient client) { 2910 mCallbackProxy.setWebChromeClient(client); 2911 } 2912 2913 /** 2914 * Gets the chrome handler. 2915 * @return the current WebChromeClient instance. 2916 * 2917 * @hide API council approval. 2918 */ 2919 public WebChromeClient getWebChromeClient() { 2920 return mCallbackProxy.getWebChromeClient(); 2921 } 2922 2923 /** 2924 * Set the back/forward list client. This is an implementation of 2925 * WebBackForwardListClient for handling new items and changes in the 2926 * history index. 2927 * @param client An implementation of WebBackForwardListClient. 2928 * {@hide} 2929 */ 2930 public void setWebBackForwardListClient(WebBackForwardListClient client) { 2931 mCallbackProxy.setWebBackForwardListClient(client); 2932 } 2933 2934 /** 2935 * Gets the WebBackForwardListClient. 2936 * {@hide} 2937 */ 2938 public WebBackForwardListClient getWebBackForwardListClient() { 2939 return mCallbackProxy.getWebBackForwardListClient(); 2940 } 2941 2942 /** 2943 * Set the Picture listener. This is an interface used to receive 2944 * notifications of a new Picture. 2945 * @param listener An implementation of WebView.PictureListener. 2946 */ 2947 public void setPictureListener(PictureListener listener) { 2948 mPictureListener = listener; 2949 } 2950 2951 /** 2952 * {@hide} 2953 */ 2954 /* FIXME: Debug only! Remove for SDK! */ 2955 public void externalRepresentation(Message callback) { 2956 mWebViewCore.sendMessage(EventHub.REQUEST_EXT_REPRESENTATION, callback); 2957 } 2958 2959 /** 2960 * {@hide} 2961 */ 2962 /* FIXME: Debug only! Remove for SDK! */ 2963 public void documentAsText(Message callback) { 2964 mWebViewCore.sendMessage(EventHub.REQUEST_DOC_AS_TEXT, callback); 2965 } 2966 2967 /** 2968 * Use this function to bind an object to Javascript so that the 2969 * methods can be accessed from Javascript. 2970 * <p><strong>IMPORTANT:</strong> 2971 * <ul> 2972 * <li> Using addJavascriptInterface() allows JavaScript to control your 2973 * application. This can be a very useful feature or a dangerous security 2974 * issue. When the HTML in the WebView is untrustworthy (for example, part 2975 * or all of the HTML is provided by some person or process), then an 2976 * attacker could inject HTML that will execute your code and possibly any 2977 * code of the attacker's choosing.<br> 2978 * Do not use addJavascriptInterface() unless all of the HTML in this 2979 * WebView was written by you.</li> 2980 * <li> The Java object that is bound runs in another thread and not in 2981 * the thread that it was constructed in.</li> 2982 * </ul></p> 2983 * @param obj The class instance to bind to Javascript 2984 * @param interfaceName The name to used to expose the class in Javascript 2985 */ 2986 public void addJavascriptInterface(Object obj, String interfaceName) { 2987 WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData(); 2988 arg.mObject = obj; 2989 arg.mInterfaceName = interfaceName; 2990 mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg); 2991 } 2992 2993 /** 2994 * Return the WebSettings object used to control the settings for this 2995 * WebView. 2996 * @return A WebSettings object that can be used to control this WebView's 2997 * settings. 2998 */ 2999 public WebSettings getSettings() { 3000 return (mWebViewCore != null) ? mWebViewCore.getSettings() : null; 3001 } 3002 3003 /** 3004 * Use this method to inform the webview about packages that are installed 3005 * in the system. This information will be used by the 3006 * navigator.isApplicationInstalled() API. 3007 * @param packageNames is a set of package names that are known to be 3008 * installed in the system. 3009 * 3010 * @hide not a public API 3011 */ 3012 public void addPackageNames(Set<String> packageNames) { 3013 mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAMES, packageNames); 3014 } 3015 3016 /** 3017 * Use this method to inform the webview about single packages that are 3018 * installed in the system. This information will be used by the 3019 * navigator.isApplicationInstalled() API. 3020 * @param packageName is the name of a package that is known to be 3021 * installed in the system. 3022 * 3023 * @hide not a public API 3024 */ 3025 public void addPackageName(String packageName) { 3026 mWebViewCore.sendMessage(EventHub.ADD_PACKAGE_NAME, packageName); 3027 } 3028 3029 /** 3030 * Use this method to inform the webview about packages that are uninstalled 3031 * in the system. This information will be used by the 3032 * navigator.isApplicationInstalled() API. 3033 * @param packageName is the name of a package that has been uninstalled in 3034 * the system. 3035 * 3036 * @hide not a public API 3037 */ 3038 public void removePackageName(String packageName) { 3039 mWebViewCore.sendMessage(EventHub.REMOVE_PACKAGE_NAME, packageName); 3040 } 3041 3042 /** 3043 * Return the list of currently loaded plugins. 3044 * @return The list of currently loaded plugins. 3045 * 3046 * @deprecated This was used for Gears, which has been deprecated. 3047 */ 3048 @Deprecated 3049 public static synchronized PluginList getPluginList() { 3050 return new PluginList(); 3051 } 3052 3053 /** 3054 * @deprecated This was used for Gears, which has been deprecated. 3055 */ 3056 @Deprecated 3057 public void refreshPlugins(boolean reloadOpenPages) { } 3058 3059 //------------------------------------------------------------------------- 3060 // Override View methods 3061 //------------------------------------------------------------------------- 3062 3063 @Override 3064 protected void finalize() throws Throwable { 3065 try { 3066 destroy(); 3067 } finally { 3068 super.finalize(); 3069 } 3070 } 3071 3072 @Override 3073 protected boolean drawChild(Canvas canvas, View child, long drawingTime) { 3074 if (child == mTitleBar) { 3075 // When drawing the title bar, move it horizontally to always show 3076 // at the top of the WebView. 3077 mTitleBar.offsetLeftAndRight(mScrollX - mTitleBar.getLeft()); 3078 } 3079 return super.drawChild(canvas, child, drawingTime); 3080 } 3081 3082 private void drawContent(Canvas canvas) { 3083 // Update the buttons in the picture, so when we draw the picture 3084 // to the screen, they are in the correct state. 3085 // Tell the native side if user is a) touching the screen, 3086 // b) pressing the trackball down, or c) pressing the enter key 3087 // If the cursor is on a button, we need to draw it in the pressed 3088 // state. 3089 // If mNativeClass is 0, we should not reach here, so we do not 3090 // need to check it again. 3091 nativeRecordButtons(hasFocus() && hasWindowFocus(), 3092 mTouchMode == TOUCH_SHORTPRESS_START_MODE 3093 || mTrackballDown || mGotCenterDown, false); 3094 drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing); 3095 } 3096 3097 @Override 3098 protected void onDraw(Canvas canvas) { 3099 // if mNativeClass is 0, the WebView has been destroyed. Do nothing. 3100 if (mNativeClass == 0) { 3101 return; 3102 } 3103 3104 // if both mContentWidth and mContentHeight are 0, it means there is no 3105 // valid Picture passed to WebView yet. This can happen when WebView 3106 // just starts. Draw the background and return. 3107 if ((mContentWidth | mContentHeight) == 0 && mHistoryPicture == null) { 3108 canvas.drawColor(mBackgroundColor); 3109 return; 3110 } 3111 3112 int saveCount = canvas.save(); 3113 if (mTitleBar != null) { 3114 canvas.translate(0, (int) mTitleBar.getHeight()); 3115 } 3116 if (mDragTrackerHandler == null) { 3117 drawContent(canvas); 3118 } else { 3119 if (!mDragTrackerHandler.draw(canvas)) { 3120 // sometimes the tracker doesn't draw, even though its active 3121 drawContent(canvas); 3122 } 3123 if (mDragTrackerHandler.isFinished()) { 3124 mDragTrackerHandler = null; 3125 } 3126 } 3127 canvas.restoreToCount(saveCount); 3128 3129 // Now draw the shadow. 3130 int titleH = getVisibleTitleHeight(); 3131 if (mTitleBar != null && titleH == 0) { 3132 int height = (int) (5f * getContext().getResources() 3133 .getDisplayMetrics().density); 3134 mTitleShadow.setBounds(mScrollX, mScrollY, mScrollX + getWidth(), 3135 mScrollY + height); 3136 mTitleShadow.draw(canvas); 3137 } 3138 if (AUTO_REDRAW_HACK && mAutoRedraw) { 3139 invalidate(); 3140 } 3141 if (inEditingMode()) mWebTextView.onDrawSubstitute(); 3142 mWebViewCore.signalRepaintDone(); 3143 } 3144 3145 @Override 3146 public void setLayoutParams(ViewGroup.LayoutParams params) { 3147 if (params.height == LayoutParams.WRAP_CONTENT) { 3148 mWrapContent = true; 3149 } 3150 super.setLayoutParams(params); 3151 } 3152 3153 @Override 3154 public boolean performLongClick() { 3155 // performLongClick() is the result of a delayed message. If we switch 3156 // to windows overview, the WebView will be temporarily removed from the 3157 // view system. In that case, do nothing. 3158 if (getParent() == null) return false; 3159 if (mNativeClass != 0 && nativeCursorIsTextInput()) { 3160 // Send the click so that the textfield is in focus 3161 centerKeyPressOnTextField(); 3162 rebuildWebTextView(); 3163 } else { 3164 clearTextEntry(true); 3165 } 3166 if (inEditingMode()) { 3167 return mWebTextView.performLongClick(); 3168 } else { 3169 return super.performLongClick(); 3170 } 3171 } 3172 3173 boolean inAnimateZoom() { 3174 return mZoomScale != 0; 3175 } 3176 3177 /** 3178 * Need to adjust the WebTextView after a change in zoom, since mActualScale 3179 * has changed. This is especially important for password fields, which are 3180 * drawn by the WebTextView, since it conveys more information than what 3181 * webkit draws. Thus we need to reposition it to show in the correct 3182 * place. 3183 */ 3184 private boolean mNeedToAdjustWebTextView; 3185 3186 private boolean didUpdateTextViewBounds(boolean allowIntersect) { 3187 Rect contentBounds = nativeFocusCandidateNodeBounds(); 3188 Rect vBox = contentToViewRect(contentBounds); 3189 Rect visibleRect = new Rect(); 3190 calcOurVisibleRect(visibleRect); 3191 // If the textfield is on screen, place the WebTextView in 3192 // its new place, accounting for our new scroll/zoom values, 3193 // and adjust its textsize. 3194 if (allowIntersect ? Rect.intersects(visibleRect, vBox) 3195 : visibleRect.contains(vBox)) { 3196 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), 3197 vBox.height()); 3198 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 3199 contentToViewDimension( 3200 nativeFocusCandidateTextSize())); 3201 return true; 3202 } else { 3203 // The textfield is now off screen. The user probably 3204 // was not zooming to see the textfield better. Remove 3205 // the WebTextView. If the user types a key, and the 3206 // textfield is still in focus, we will reconstruct 3207 // the WebTextView and scroll it back on screen. 3208 mWebTextView.remove(); 3209 return false; 3210 } 3211 } 3212 3213 private void drawExtras(Canvas canvas, int extras, boolean animationsRunning) { 3214 // If mNativeClass is 0, we should not reach here, so we do not 3215 // need to check it again. 3216 if (animationsRunning) { 3217 canvas.setDrawFilter(mWebViewCore.mZoomFilter); 3218 } 3219 nativeDrawExtras(canvas, extras); 3220 canvas.setDrawFilter(null); 3221 } 3222 3223 private void drawCoreAndCursorRing(Canvas canvas, int color, 3224 boolean drawCursorRing) { 3225 if (mDrawHistory) { 3226 canvas.scale(mActualScale, mActualScale); 3227 canvas.drawPicture(mHistoryPicture); 3228 return; 3229 } 3230 3231 boolean animateZoom = mZoomScale != 0; 3232 boolean animateScroll = ((!mScroller.isFinished() 3233 || mVelocityTracker != null) 3234 && (mTouchMode != TOUCH_DRAG_MODE || 3235 mHeldMotionless != MOTIONLESS_TRUE)) 3236 || mDeferTouchMode == TOUCH_DRAG_MODE; 3237 if (mTouchMode == TOUCH_DRAG_MODE) { 3238 if (mHeldMotionless == MOTIONLESS_PENDING) { 3239 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 3240 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 3241 mHeldMotionless = MOTIONLESS_FALSE; 3242 } 3243 if (mHeldMotionless == MOTIONLESS_FALSE) { 3244 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3245 .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME); 3246 mHeldMotionless = MOTIONLESS_PENDING; 3247 } 3248 } 3249 if (animateZoom) { 3250 float zoomScale; 3251 int interval = (int) (SystemClock.uptimeMillis() - mZoomStart); 3252 if (interval < ZOOM_ANIMATION_LENGTH) { 3253 float ratio = (float) interval / ZOOM_ANIMATION_LENGTH; 3254 zoomScale = 1.0f / (mInvInitialZoomScale 3255 + (mInvFinalZoomScale - mInvInitialZoomScale) * ratio); 3256 invalidate(); 3257 } else { 3258 zoomScale = mZoomScale; 3259 // set mZoomScale to be 0 as we have done animation 3260 mZoomScale = 0; 3261 WebViewCore.resumeUpdatePicture(mWebViewCore); 3262 // call invalidate() again to draw with the final filters 3263 invalidate(); 3264 if (mNeedToAdjustWebTextView) { 3265 mNeedToAdjustWebTextView = false; 3266 if (didUpdateTextViewBounds(false) 3267 && nativeFocusCandidateIsPassword()) { 3268 // If it is a password field, start drawing the 3269 // WebTextView once again. 3270 mWebTextView.setInPassword(true); 3271 } 3272 } 3273 } 3274 // calculate the intermediate scroll position. As we need to use 3275 // zoomScale, we can't use pinLocX/Y directly. Copy the logic here. 3276 float scale = zoomScale * mInvInitialZoomScale; 3277 int tx = Math.round(scale * (mInitialScrollX + mZoomCenterX) 3278 - mZoomCenterX); 3279 tx = -pinLoc(tx, getViewWidth(), Math.round(mContentWidth 3280 * zoomScale)) + mScrollX; 3281 int titleHeight = getTitleHeight(); 3282 int ty = Math.round(scale 3283 * (mInitialScrollY + mZoomCenterY - titleHeight) 3284 - (mZoomCenterY - titleHeight)); 3285 ty = -(ty <= titleHeight ? Math.max(ty, 0) : pinLoc(ty 3286 - titleHeight, getViewHeight(), Math.round(mContentHeight 3287 * zoomScale)) + titleHeight) + mScrollY; 3288 canvas.translate(tx, ty); 3289 canvas.scale(zoomScale, zoomScale); 3290 if (inEditingMode() && !mNeedToAdjustWebTextView 3291 && mZoomScale != 0) { 3292 // The WebTextView is up. Keep track of this so we can adjust 3293 // its size and placement when we finish zooming 3294 mNeedToAdjustWebTextView = true; 3295 // If it is in password mode, turn it off so it does not draw 3296 // misplaced. 3297 if (nativeFocusCandidateIsPassword()) { 3298 mWebTextView.setInPassword(false); 3299 } 3300 } 3301 } else { 3302 canvas.scale(mActualScale, mActualScale); 3303 } 3304 3305 boolean UIAnimationsRunning = false; 3306 // Currently for each draw we compute the animation values; 3307 // We may in the future decide to do that independently. 3308 if (mNativeClass != 0 && nativeEvaluateLayersAnimations()) { 3309 UIAnimationsRunning = true; 3310 // If we have unfinished (or unstarted) animations, 3311 // we ask for a repaint. 3312 invalidate(); 3313 } 3314 mWebViewCore.drawContentPicture(canvas, color, 3315 (animateZoom || mPreviewZoomOnly || UIAnimationsRunning), 3316 animateScroll); 3317 if (mNativeClass == 0) return; 3318 // decide which adornments to draw 3319 int extras = DRAW_EXTRAS_NONE; 3320 if (mFindIsUp) { 3321 // When the FindDialog is up, only draw the matches if we are not in 3322 // the process of scrolling them into view. 3323 if (!animateScroll) { 3324 extras = DRAW_EXTRAS_FIND; 3325 } 3326 } else if (mShiftIsPressed 3327 && !nativePageShouldHandleShiftAndArrows()) { 3328 if (!animateZoom && !mPreviewZoomOnly) { 3329 extras = DRAW_EXTRAS_SELECTION; 3330 nativeSetSelectionRegion(mTouchSelection || mExtendSelection); 3331 nativeSetSelectionPointer(!mTouchSelection, mInvActualScale, 3332 mSelectX, mSelectY - getTitleHeight(), 3333 mExtendSelection); 3334 } 3335 } else if (drawCursorRing) { 3336 extras = DRAW_EXTRAS_CURSOR_RING; 3337 } 3338 drawExtras(canvas, extras, UIAnimationsRunning); 3339 3340 if (extras == DRAW_EXTRAS_CURSOR_RING) { 3341 if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { 3342 mTouchMode = TOUCH_SHORTPRESS_MODE; 3343 HitTestResult hitTest = getHitTestResult(); 3344 if (hitTest == null 3345 || hitTest.mType == HitTestResult.UNKNOWN_TYPE) { 3346 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 3347 } 3348 } 3349 } 3350 if (mFocusSizeChanged) { 3351 mFocusSizeChanged = false; 3352 // If we are zooming, this will get handled above, when the zoom 3353 // finishes. We also do not need to do this unless the WebTextView 3354 // is showing. 3355 if (!animateZoom && inEditingMode()) { 3356 didUpdateTextViewBounds(true); 3357 } 3358 } 3359 } 3360 3361 // draw history 3362 private boolean mDrawHistory = false; 3363 private Picture mHistoryPicture = null; 3364 private int mHistoryWidth = 0; 3365 private int mHistoryHeight = 0; 3366 3367 // Only check the flag, can be called from WebCore thread 3368 boolean drawHistory() { 3369 return mDrawHistory; 3370 } 3371 3372 // Should only be called in UI thread 3373 void switchOutDrawHistory() { 3374 if (null == mWebViewCore) return; // CallbackProxy may trigger this 3375 if (mDrawHistory && mWebViewCore.pictureReady()) { 3376 mDrawHistory = false; 3377 mHistoryPicture = null; 3378 invalidate(); 3379 int oldScrollX = mScrollX; 3380 int oldScrollY = mScrollY; 3381 mScrollX = pinLocX(mScrollX); 3382 mScrollY = pinLocY(mScrollY); 3383 if (oldScrollX != mScrollX || oldScrollY != mScrollY) { 3384 mUserScroll = false; 3385 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, oldScrollX, 3386 oldScrollY); 3387 onScrollChanged(mScrollX, mScrollY, oldScrollX, oldScrollY); 3388 } else { 3389 sendOurVisibleRect(); 3390 } 3391 } 3392 } 3393 3394 WebViewCore.CursorData cursorData() { 3395 WebViewCore.CursorData result = new WebViewCore.CursorData(); 3396 result.mMoveGeneration = nativeMoveGeneration(); 3397 result.mFrame = nativeCursorFramePointer(); 3398 Point position = nativeCursorPosition(); 3399 result.mX = position.x; 3400 result.mY = position.y; 3401 return result; 3402 } 3403 3404 /** 3405 * Delete text from start to end in the focused textfield. If there is no 3406 * focus, or if start == end, silently fail. If start and end are out of 3407 * order, swap them. 3408 * @param start Beginning of selection to delete. 3409 * @param end End of selection to delete. 3410 */ 3411 /* package */ void deleteSelection(int start, int end) { 3412 mTextGeneration++; 3413 WebViewCore.TextSelectionData data 3414 = new WebViewCore.TextSelectionData(start, end); 3415 mWebViewCore.sendMessage(EventHub.DELETE_SELECTION, mTextGeneration, 0, 3416 data); 3417 } 3418 3419 /** 3420 * Set the selection to (start, end) in the focused textfield. If start and 3421 * end are out of order, swap them. 3422 * @param start Beginning of selection. 3423 * @param end End of selection. 3424 */ 3425 /* package */ void setSelection(int start, int end) { 3426 mWebViewCore.sendMessage(EventHub.SET_SELECTION, start, end); 3427 } 3428 3429 @Override 3430 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 3431 InputConnection connection = super.onCreateInputConnection(outAttrs); 3432 outAttrs.imeOptions |= EditorInfo.IME_FLAG_NO_FULLSCREEN; 3433 return connection; 3434 } 3435 3436 /** 3437 * Called in response to a message from webkit telling us that the soft 3438 * keyboard should be launched. 3439 */ 3440 private void displaySoftKeyboard(boolean isTextView) { 3441 InputMethodManager imm = (InputMethodManager) 3442 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 3443 3444 // bring it back to the default scale so that user can enter text 3445 boolean zoom = mActualScale < mDefaultScale; 3446 if (zoom) { 3447 mZoomManager.mInZoomOverview = false; 3448 mZoomCenterX = mLastTouchX; 3449 mZoomCenterY = mLastTouchY; 3450 // do not change text wrap scale so that there is no reflow 3451 setNewZoomScale(mDefaultScale, false, false); 3452 } 3453 if (isTextView) { 3454 rebuildWebTextView(); 3455 if (inEditingMode()) { 3456 imm.showSoftInput(mWebTextView, 0); 3457 if (zoom) { 3458 didUpdateTextViewBounds(true); 3459 } 3460 return; 3461 } 3462 } 3463 // Used by plugins. 3464 // Also used if the navigation cache is out of date, and 3465 // does not recognize that a textfield is in focus. In that 3466 // case, use WebView as the targeted view. 3467 // see http://b/issue?id=2457459 3468 imm.showSoftInput(this, 0); 3469 } 3470 3471 // Called by WebKit to instruct the UI to hide the keyboard 3472 private void hideSoftKeyboard() { 3473 InputMethodManager imm = (InputMethodManager) 3474 getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 3475 3476 imm.hideSoftInputFromWindow(this.getWindowToken(), 0); 3477 } 3478 3479 /* 3480 * This method checks the current focus and cursor and potentially rebuilds 3481 * mWebTextView to have the appropriate properties, such as password, 3482 * multiline, and what text it contains. It also removes it if necessary. 3483 */ 3484 /* package */ void rebuildWebTextView() { 3485 // If the WebView does not have focus, do nothing until it gains focus. 3486 if (!hasFocus() && (null == mWebTextView || !mWebTextView.hasFocus())) { 3487 return; 3488 } 3489 boolean alreadyThere = inEditingMode(); 3490 // inEditingMode can only return true if mWebTextView is non-null, 3491 // so we can safely call remove() if (alreadyThere) 3492 if (0 == mNativeClass || !nativeFocusCandidateIsTextInput()) { 3493 if (alreadyThere) { 3494 mWebTextView.remove(); 3495 } 3496 return; 3497 } 3498 // At this point, we know we have found an input field, so go ahead 3499 // and create the WebTextView if necessary. 3500 if (mWebTextView == null) { 3501 mWebTextView = new WebTextView(mContext, WebView.this); 3502 // Initialize our generation number. 3503 mTextGeneration = 0; 3504 } 3505 mWebTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, 3506 contentToViewDimension(nativeFocusCandidateTextSize())); 3507 Rect visibleRect = new Rect(); 3508 calcOurContentVisibleRect(visibleRect); 3509 // Note that sendOurVisibleRect calls viewToContent, so the coordinates 3510 // should be in content coordinates. 3511 Rect bounds = nativeFocusCandidateNodeBounds(); 3512 Rect vBox = contentToViewRect(bounds); 3513 mWebTextView.setRect(vBox.left, vBox.top, vBox.width(), vBox.height()); 3514 if (!Rect.intersects(bounds, visibleRect)) { 3515 mWebTextView.bringIntoView(); 3516 } 3517 String text = nativeFocusCandidateText(); 3518 int nodePointer = nativeFocusCandidatePointer(); 3519 if (alreadyThere && mWebTextView.isSameTextField(nodePointer)) { 3520 // It is possible that we have the same textfield, but it has moved, 3521 // i.e. In the case of opening/closing the screen. 3522 // In that case, we need to set the dimensions, but not the other 3523 // aspects. 3524 // If the text has been changed by webkit, update it. However, if 3525 // there has been more UI text input, ignore it. We will receive 3526 // another update when that text is recognized. 3527 if (text != null && !text.equals(mWebTextView.getText().toString()) 3528 && nativeTextGeneration() == mTextGeneration) { 3529 mWebTextView.setTextAndKeepSelection(text); 3530 } 3531 } else { 3532 mWebTextView.setGravity(nativeFocusCandidateIsRtlText() ? 3533 Gravity.RIGHT : Gravity.NO_GRAVITY); 3534 // This needs to be called before setType, which may call 3535 // requestFormData, and it needs to have the correct nodePointer. 3536 mWebTextView.setNodePointer(nodePointer); 3537 mWebTextView.setType(nativeFocusCandidateType()); 3538 if (null == text) { 3539 if (DebugFlags.WEB_VIEW) { 3540 Log.v(LOGTAG, "rebuildWebTextView null == text"); 3541 } 3542 text = ""; 3543 } 3544 mWebTextView.setTextAndKeepSelection(text); 3545 InputMethodManager imm = InputMethodManager.peekInstance(); 3546 if (imm != null && imm.isActive(mWebTextView)) { 3547 imm.restartInput(mWebTextView); 3548 } 3549 } 3550 mWebTextView.requestFocus(); 3551 } 3552 3553 /** 3554 * Called by WebTextView to find saved form data associated with the 3555 * textfield 3556 * @param name Name of the textfield. 3557 * @param nodePointer Pointer to the node of the textfield, so it can be 3558 * compared to the currently focused textfield when the data is 3559 * retrieved. 3560 */ 3561 /* package */ void requestFormData(String name, int nodePointer) { 3562 if (mWebViewCore.getSettings().getSaveFormData()) { 3563 Message update = mPrivateHandler.obtainMessage(REQUEST_FORM_DATA); 3564 update.arg1 = nodePointer; 3565 RequestFormData updater = new RequestFormData(name, getUrl(), 3566 update); 3567 Thread t = new Thread(updater); 3568 t.start(); 3569 } 3570 } 3571 3572 /** 3573 * Pass a message to find out the <label> associated with the <input> 3574 * identified by nodePointer 3575 * @param framePointer Pointer to the frame containing the <input> node 3576 * @param nodePointer Pointer to the node for which a <label> is desired. 3577 */ 3578 /* package */ void requestLabel(int framePointer, int nodePointer) { 3579 mWebViewCore.sendMessage(EventHub.REQUEST_LABEL, framePointer, 3580 nodePointer); 3581 } 3582 3583 /* 3584 * This class requests an Adapter for the WebTextView which shows past 3585 * entries stored in the database. It is a Runnable so that it can be done 3586 * in its own thread, without slowing down the UI. 3587 */ 3588 private class RequestFormData implements Runnable { 3589 private String mName; 3590 private String mUrl; 3591 private Message mUpdateMessage; 3592 3593 public RequestFormData(String name, String url, Message msg) { 3594 mName = name; 3595 mUrl = url; 3596 mUpdateMessage = msg; 3597 } 3598 3599 public void run() { 3600 ArrayList<String> pastEntries = mDatabase.getFormData(mUrl, mName); 3601 if (pastEntries.size() > 0) { 3602 AutoCompleteAdapter adapter = new 3603 AutoCompleteAdapter(mContext, pastEntries); 3604 mUpdateMessage.obj = adapter; 3605 mUpdateMessage.sendToTarget(); 3606 } 3607 } 3608 } 3609 3610 /** 3611 * Dump the display tree to "/sdcard/displayTree.txt" 3612 * 3613 * @hide debug only 3614 */ 3615 public void dumpDisplayTree() { 3616 nativeDumpDisplayTree(getUrl()); 3617 } 3618 3619 /** 3620 * Dump the dom tree to adb shell if "toFile" is False, otherwise dump it to 3621 * "/sdcard/domTree.txt" 3622 * 3623 * @hide debug only 3624 */ 3625 public void dumpDomTree(boolean toFile) { 3626 mWebViewCore.sendMessage(EventHub.DUMP_DOMTREE, toFile ? 1 : 0, 0); 3627 } 3628 3629 /** 3630 * Dump the render tree to adb shell if "toFile" is False, otherwise dump it 3631 * to "/sdcard/renderTree.txt" 3632 * 3633 * @hide debug only 3634 */ 3635 public void dumpRenderTree(boolean toFile) { 3636 mWebViewCore.sendMessage(EventHub.DUMP_RENDERTREE, toFile ? 1 : 0, 0); 3637 } 3638 3639 /** 3640 * Dump the V8 counters to standard output. 3641 * Note that you need a build with V8 and WEBCORE_INSTRUMENTATION set to 3642 * true. Otherwise, this will do nothing. 3643 * 3644 * @hide debug only 3645 */ 3646 public void dumpV8Counters() { 3647 mWebViewCore.sendMessage(EventHub.DUMP_V8COUNTERS); 3648 } 3649 3650 // This is used to determine long press with the center key. Does not 3651 // affect long press with the trackball/touch. 3652 private boolean mGotCenterDown = false; 3653 3654 @Override 3655 public boolean onKeyDown(int keyCode, KeyEvent event) { 3656 if (DebugFlags.WEB_VIEW) { 3657 Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() 3658 + ", " + event + ", unicode=" + event.getUnicodeChar()); 3659 } 3660 3661 if (mNativeClass == 0) { 3662 return false; 3663 } 3664 3665 // do this hack up front, so it always works, regardless of touch-mode 3666 if (AUTO_REDRAW_HACK && (keyCode == KeyEvent.KEYCODE_CALL)) { 3667 mAutoRedraw = !mAutoRedraw; 3668 if (mAutoRedraw) { 3669 invalidate(); 3670 } 3671 return true; 3672 } 3673 3674 // Bubble up the key event if 3675 // 1. it is a system key; or 3676 // 2. the host application wants to handle it; 3677 // 3. the accessibility injector is present and wants to handle it; 3678 if (event.isSystem() 3679 || mCallbackProxy.uiOverrideKeyEvent(event) 3680 || (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event))) { 3681 return false; 3682 } 3683 3684 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 3685 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 3686 if (nativePageShouldHandleShiftAndArrows()) { 3687 mShiftIsPressed = true; 3688 } else if (!nativeCursorWantsKeyEvents() && !mShiftIsPressed) { 3689 setUpSelectXY(); 3690 } 3691 } 3692 3693 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 3694 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 3695 switchOutDrawHistory(); 3696 if (nativePageShouldHandleShiftAndArrows()) { 3697 letPageHandleNavKey(keyCode, event.getEventTime(), true); 3698 return true; 3699 } 3700 if (mShiftIsPressed) { 3701 int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT 3702 ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0; 3703 int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ? 3704 -1 : keyCode == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : 0; 3705 int multiplier = event.getRepeatCount() + 1; 3706 moveSelection(xRate * multiplier, yRate * multiplier); 3707 return true; 3708 } 3709 if (navHandledKey(keyCode, 1, false, event.getEventTime())) { 3710 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 3711 return true; 3712 } 3713 // Bubble up the key event as WebView doesn't handle it 3714 return false; 3715 } 3716 3717 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 3718 switchOutDrawHistory(); 3719 if (event.getRepeatCount() == 0) { 3720 if (mShiftIsPressed 3721 && !nativePageShouldHandleShiftAndArrows()) { 3722 return true; // discard press if copy in progress 3723 } 3724 mGotCenterDown = true; 3725 mPrivateHandler.sendMessageDelayed(mPrivateHandler 3726 .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); 3727 // Already checked mNativeClass, so we do not need to check it 3728 // again. 3729 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 3730 return true; 3731 } 3732 // Bubble up the key event as WebView doesn't handle it 3733 return false; 3734 } 3735 3736 if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT 3737 && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) { 3738 // turn off copy select if a shift-key combo is pressed 3739 mExtendSelection = mShiftIsPressed = false; 3740 if (mTouchMode == TOUCH_SELECT_MODE) { 3741 mTouchMode = TOUCH_INIT_MODE; 3742 } 3743 } 3744 3745 if (getSettings().getNavDump()) { 3746 switch (keyCode) { 3747 case KeyEvent.KEYCODE_4: 3748 dumpDisplayTree(); 3749 break; 3750 case KeyEvent.KEYCODE_5: 3751 case KeyEvent.KEYCODE_6: 3752 dumpDomTree(keyCode == KeyEvent.KEYCODE_5); 3753 break; 3754 case KeyEvent.KEYCODE_7: 3755 case KeyEvent.KEYCODE_8: 3756 dumpRenderTree(keyCode == KeyEvent.KEYCODE_7); 3757 break; 3758 case KeyEvent.KEYCODE_9: 3759 nativeInstrumentReport(); 3760 return true; 3761 } 3762 } 3763 3764 if (nativeCursorIsTextInput()) { 3765 // This message will put the node in focus, for the DOM's notion 3766 // of focus, and make the focuscontroller active 3767 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 3768 nativeCursorNodePointer()); 3769 // This will bring up the WebTextView and put it in focus, for 3770 // our view system's notion of focus 3771 rebuildWebTextView(); 3772 // Now we need to pass the event to it 3773 if (inEditingMode()) { 3774 mWebTextView.setDefaultSelection(); 3775 return mWebTextView.dispatchKeyEvent(event); 3776 } 3777 } else if (nativeHasFocusNode()) { 3778 // In this case, the cursor is not on a text input, but the focus 3779 // might be. Check it, and if so, hand over to the WebTextView. 3780 rebuildWebTextView(); 3781 if (inEditingMode()) { 3782 mWebTextView.setDefaultSelection(); 3783 return mWebTextView.dispatchKeyEvent(event); 3784 } 3785 } 3786 3787 // TODO: should we pass all the keys to DOM or check the meta tag 3788 if (nativeCursorWantsKeyEvents() || true) { 3789 // pass the key to DOM 3790 mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); 3791 // return true as DOM handles the key 3792 return true; 3793 } 3794 3795 // Bubble up the key event as WebView doesn't handle it 3796 return false; 3797 } 3798 3799 @Override 3800 public boolean onKeyUp(int keyCode, KeyEvent event) { 3801 if (DebugFlags.WEB_VIEW) { 3802 Log.v(LOGTAG, "keyUp at " + System.currentTimeMillis() 3803 + ", " + event + ", unicode=" + event.getUnicodeChar()); 3804 } 3805 3806 if (mNativeClass == 0) { 3807 return false; 3808 } 3809 3810 // special CALL handling when cursor node's href is "tel:XXX" 3811 if (keyCode == KeyEvent.KEYCODE_CALL && nativeHasCursorNode()) { 3812 String text = nativeCursorText(); 3813 if (!nativeCursorIsTextInput() && text != null 3814 && text.startsWith(SCHEME_TEL)) { 3815 Intent intent = new Intent(Intent.ACTION_DIAL, Uri.parse(text)); 3816 getContext().startActivity(intent); 3817 return true; 3818 } 3819 } 3820 3821 // Bubble up the key event if 3822 // 1. it is a system key; or 3823 // 2. the host application wants to handle it; 3824 // 3. the accessibility injector is present and wants to handle it; 3825 if (event.isSystem() 3826 || mCallbackProxy.uiOverrideKeyEvent(event) 3827 || (mAccessibilityInjector != null && mAccessibilityInjector.onKeyEvent(event))) { 3828 return false; 3829 } 3830 3831 if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT 3832 || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { 3833 if (nativePageShouldHandleShiftAndArrows()) { 3834 mShiftIsPressed = false; 3835 } else if (commitCopy()) { 3836 return true; 3837 } 3838 } 3839 3840 if (keyCode >= KeyEvent.KEYCODE_DPAD_UP 3841 && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { 3842 if (nativePageShouldHandleShiftAndArrows()) { 3843 letPageHandleNavKey(keyCode, event.getEventTime(), false); 3844 return true; 3845 } 3846 // always handle the navigation keys in the UI thread 3847 // Bubble up the key event as WebView doesn't handle it 3848 return false; 3849 } 3850 3851 if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { 3852 // remove the long press message first 3853 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 3854 mGotCenterDown = false; 3855 3856 if (mShiftIsPressed && !nativePageShouldHandleShiftAndArrows()) { 3857 if (mExtendSelection) { 3858 commitCopy(); 3859 } else { 3860 mExtendSelection = true; 3861 invalidate(); // draw the i-beam instead of the arrow 3862 } 3863 return true; // discard press if copy in progress 3864 } 3865 3866 // perform the single click 3867 Rect visibleRect = sendOurVisibleRect(); 3868 // Note that sendOurVisibleRect calls viewToContent, so the 3869 // coordinates should be in content coordinates. 3870 if (!nativeCursorIntersects(visibleRect)) { 3871 return false; 3872 } 3873 WebViewCore.CursorData data = cursorData(); 3874 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, data); 3875 playSoundEffect(SoundEffectConstants.CLICK); 3876 if (nativeCursorIsTextInput()) { 3877 rebuildWebTextView(); 3878 centerKeyPressOnTextField(); 3879 if (inEditingMode()) { 3880 mWebTextView.setDefaultSelection(); 3881 } 3882 return true; 3883 } 3884 clearTextEntry(true); 3885 nativeSetFollowedLink(true); 3886 if (!mCallbackProxy.uiOverrideUrlLoading(nativeCursorText())) { 3887 mWebViewCore.sendMessage(EventHub.CLICK, data.mFrame, 3888 nativeCursorNodePointer()); 3889 } 3890 return true; 3891 } 3892 3893 // TODO: should we pass all the keys to DOM or check the meta tag 3894 if (nativeCursorWantsKeyEvents() || true) { 3895 // pass the key to DOM 3896 mWebViewCore.sendMessage(EventHub.KEY_UP, event); 3897 // return true as DOM handles the key 3898 return true; 3899 } 3900 3901 // Bubble up the key event as WebView doesn't handle it 3902 return false; 3903 } 3904 3905 private void setUpSelectXY() { 3906 mExtendSelection = false; 3907 mShiftIsPressed = true; 3908 if (nativeHasCursorNode()) { 3909 Rect rect = nativeCursorNodeBounds(); 3910 mSelectX = contentToViewX(rect.left); 3911 mSelectY = contentToViewY(rect.top); 3912 } else if (mLastTouchY > getVisibleTitleHeight()) { 3913 mSelectX = mScrollX + (int) mLastTouchX; 3914 mSelectY = mScrollY + (int) mLastTouchY; 3915 } else { 3916 mSelectX = mScrollX + getViewWidth() / 2; 3917 mSelectY = mScrollY + getViewHeightWithTitle() / 2; 3918 } 3919 nativeHideCursor(); 3920 } 3921 3922 /** 3923 * Use this method to put the WebView into text selection mode. 3924 * Do not rely on this functionality; it will be deprecated in the future. 3925 */ 3926 public void emulateShiftHeld() { 3927 if (0 == mNativeClass) return; // client isn't initialized 3928 setUpSelectXY(); 3929 } 3930 3931 private boolean commitCopy() { 3932 boolean copiedSomething = false; 3933 if (mExtendSelection) { 3934 String selection = nativeGetSelection(); 3935 if (selection != "") { 3936 if (DebugFlags.WEB_VIEW) { 3937 Log.v(LOGTAG, "commitCopy \"" + selection + "\""); 3938 } 3939 Toast.makeText(mContext 3940 , com.android.internal.R.string.text_copied 3941 , Toast.LENGTH_SHORT).show(); 3942 copiedSomething = true; 3943 try { 3944 IClipboard clip = IClipboard.Stub.asInterface( 3945 ServiceManager.getService("clipboard")); 3946 clip.setClipboardText(selection); 3947 } catch (android.os.RemoteException e) { 3948 Log.e(LOGTAG, "Clipboard failed", e); 3949 } 3950 } 3951 mExtendSelection = false; 3952 } 3953 mShiftIsPressed = false; 3954 invalidate(); // remove selection region and pointer 3955 if (mTouchMode == TOUCH_SELECT_MODE) { 3956 mTouchMode = TOUCH_INIT_MODE; 3957 } 3958 return copiedSomething; 3959 } 3960 3961 @Override 3962 protected void onAttachedToWindow() { 3963 super.onAttachedToWindow(); 3964 if (hasWindowFocus()) setActive(true); 3965 } 3966 3967 @Override 3968 protected void onDetachedFromWindow() { 3969 clearTextEntry(false); 3970 mZoomManager.dismissZoomPicker(); 3971 if (hasWindowFocus()) setActive(false); 3972 super.onDetachedFromWindow(); 3973 } 3974 3975 @Override 3976 protected void onVisibilityChanged(View changedView, int visibility) { 3977 super.onVisibilityChanged(changedView, visibility); 3978 if (visibility != View.VISIBLE) { 3979 mZoomManager.dismissZoomPicker(); 3980 } 3981 } 3982 3983 /** 3984 * @deprecated WebView no longer needs to implement 3985 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 3986 */ 3987 @Deprecated 3988 public void onChildViewAdded(View parent, View child) {} 3989 3990 /** 3991 * @deprecated WebView no longer needs to implement 3992 * ViewGroup.OnHierarchyChangeListener. This method does nothing now. 3993 */ 3994 @Deprecated 3995 public void onChildViewRemoved(View p, View child) {} 3996 3997 /** 3998 * @deprecated WebView should not have implemented 3999 * ViewTreeObserver.OnGlobalFocusChangeListener. This method 4000 * does nothing now. 4001 */ 4002 @Deprecated 4003 public void onGlobalFocusChanged(View oldFocus, View newFocus) { 4004 } 4005 4006 private void setActive(boolean active) { 4007 if (active) { 4008 if (hasFocus()) { 4009 // If our window regained focus, and we have focus, then begin 4010 // drawing the cursor ring 4011 mDrawCursorRing = true; 4012 if (mNativeClass != 0) { 4013 nativeRecordButtons(true, false, true); 4014 if (inEditingMode()) { 4015 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 1, 0); 4016 } 4017 } 4018 } else { 4019 // If our window gained focus, but we do not have it, do not 4020 // draw the cursor ring. 4021 mDrawCursorRing = false; 4022 // We do not call nativeRecordButtons here because we assume 4023 // that when we lost focus, or window focus, it got called with 4024 // false for the first parameter 4025 } 4026 } else { 4027 if (!mZoomManager.isZoomPickerVisible()) { 4028 /* 4029 * The external zoom controls come in their own window, so our 4030 * window loses focus. Our policy is to not draw the cursor ring 4031 * if our window is not focused, but this is an exception since 4032 * the user can still navigate the web page with the zoom 4033 * controls showing. 4034 */ 4035 mDrawCursorRing = false; 4036 } 4037 mGotKeyDown = false; 4038 mShiftIsPressed = false; 4039 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 4040 mTouchMode = TOUCH_DONE_MODE; 4041 if (mNativeClass != 0) { 4042 nativeRecordButtons(false, false, true); 4043 } 4044 setFocusControllerInactive(); 4045 } 4046 invalidate(); 4047 } 4048 4049 // To avoid drawing the cursor ring, and remove the TextView when our window 4050 // loses focus. 4051 @Override 4052 public void onWindowFocusChanged(boolean hasWindowFocus) { 4053 setActive(hasWindowFocus); 4054 if (hasWindowFocus) { 4055 BrowserFrame.sJavaBridge.setActiveWebView(this); 4056 } else { 4057 BrowserFrame.sJavaBridge.removeActiveWebView(this); 4058 } 4059 super.onWindowFocusChanged(hasWindowFocus); 4060 } 4061 4062 /* 4063 * Pass a message to WebCore Thread, telling the WebCore::Page's 4064 * FocusController to be "inactive" so that it will 4065 * not draw the blinking cursor. It gets set to "active" to draw the cursor 4066 * in WebViewCore.cpp, when the WebCore thread receives key events/clicks. 4067 */ 4068 /* package */ void setFocusControllerInactive() { 4069 // Do not need to also check whether mWebViewCore is null, because 4070 // mNativeClass is only set if mWebViewCore is non null 4071 if (mNativeClass == 0) return; 4072 mWebViewCore.sendMessage(EventHub.SET_ACTIVE, 0, 0); 4073 } 4074 4075 @Override 4076 protected void onFocusChanged(boolean focused, int direction, 4077 Rect previouslyFocusedRect) { 4078 if (DebugFlags.WEB_VIEW) { 4079 Log.v(LOGTAG, "MT focusChanged " + focused + ", " + direction); 4080 } 4081 if (focused) { 4082 // When we regain focus, if we have window focus, resume drawing 4083 // the cursor ring 4084 if (hasWindowFocus()) { 4085 mDrawCursorRing = true; 4086 if (mNativeClass != 0) { 4087 nativeRecordButtons(true, false, true); 4088 } 4089 //} else { 4090 // The WebView has gained focus while we do not have 4091 // windowfocus. When our window lost focus, we should have 4092 // called nativeRecordButtons(false...) 4093 } 4094 } else { 4095 // When we lost focus, unless focus went to the TextView (which is 4096 // true if we are in editing mode), stop drawing the cursor ring. 4097 if (!inEditingMode()) { 4098 mDrawCursorRing = false; 4099 if (mNativeClass != 0) { 4100 nativeRecordButtons(false, false, true); 4101 } 4102 setFocusControllerInactive(); 4103 } 4104 mGotKeyDown = false; 4105 } 4106 4107 super.onFocusChanged(focused, direction, previouslyFocusedRect); 4108 } 4109 4110 /** 4111 * @hide 4112 */ 4113 @Override 4114 protected boolean setFrame(int left, int top, int right, int bottom) { 4115 boolean changed = super.setFrame(left, top, right, bottom); 4116 if (!changed && mHeightCanMeasure) { 4117 // When mHeightCanMeasure is true, we will set mLastHeightSent to 0 4118 // in WebViewCore after we get the first layout. We do call 4119 // requestLayout() when we get contentSizeChanged(). But the View 4120 // system won't call onSizeChanged if the dimension is not changed. 4121 // In this case, we need to call sendViewSizeZoom() explicitly to 4122 // notify the WebKit about the new dimensions. 4123 sendViewSizeZoom(); 4124 } 4125 return changed; 4126 } 4127 4128 private static class PostScale implements Runnable { 4129 final WebView mWebView; 4130 final boolean mUpdateTextWrap; 4131 4132 public PostScale(WebView webView, boolean updateTextWrap) { 4133 mWebView = webView; 4134 mUpdateTextWrap = updateTextWrap; 4135 } 4136 4137 public void run() { 4138 if (mWebView.mWebViewCore != null) { 4139 // we always force, in case our height changed, in which case we 4140 // still want to send the notification over to webkit. 4141 mWebView.setNewZoomScale(mWebView.mActualScale, 4142 mUpdateTextWrap, true); 4143 // update the zoom buttons as the scale can be changed 4144 mWebView.mZoomManager.updateZoomPicker(); 4145 } 4146 } 4147 } 4148 4149 @Override 4150 protected void onSizeChanged(int w, int h, int ow, int oh) { 4151 super.onSizeChanged(w, h, ow, oh); 4152 // Center zooming to the center of the screen. 4153 if (mZoomScale == 0) { // unless we're already zooming 4154 // To anchor at top left corner. 4155 mZoomCenterX = 0; 4156 mZoomCenterY = getVisibleTitleHeight(); 4157 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 4158 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 4159 } 4160 4161 // adjust the max viewport width depending on the view dimensions. This 4162 // is to ensure the scaling is not going insane. So do not shrink it if 4163 // the view size is temporarily smaller, e.g. when soft keyboard is up. 4164 int newMaxViewportWidth = (int) (Math.max(w, h) / mZoomManager.DEFAULT_MIN_ZOOM_SCALE); 4165 if (newMaxViewportWidth > sMaxViewportWidth) { 4166 sMaxViewportWidth = newMaxViewportWidth; 4167 } 4168 4169 // update mMinZoomScale if the minimum zoom scale is not fixed 4170 if (!mZoomManager.mMinZoomScaleFixed) { 4171 // when change from narrow screen to wide screen, the new viewWidth 4172 // can be wider than the old content width. We limit the minimum 4173 // scale to 1.0f. The proper minimum scale will be calculated when 4174 // the new picture shows up. 4175 mZoomManager.mMinZoomScale = Math.min(1.0f, (float) getViewWidth() 4176 / (mDrawHistory ? mHistoryPicture.getWidth() 4177 : mZoomOverviewWidth)); 4178 if (mInitialScaleInPercent > 0) { 4179 // limit the minZoomScale to the initialScale if it is set 4180 float initialScale = mInitialScaleInPercent / 100.0f; 4181 if (mZoomManager.mMinZoomScale > initialScale) { 4182 mZoomManager.mMinZoomScale = initialScale; 4183 } 4184 } 4185 } 4186 4187 mZoomManager.dismissZoomPicker(); 4188 4189 // onSizeChanged() is called during WebView layout. And any 4190 // requestLayout() is blocked during layout. As setNewZoomScale() will 4191 // call its child View to reposition itself through ViewManager's 4192 // scaleAll(), we need to post a Runnable to ensure requestLayout(). 4193 // <b/> 4194 // only update the text wrap scale if width changed. 4195 post(new PostScale(this, w != ow)); 4196 } 4197 4198 @Override 4199 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 4200 super.onScrollChanged(l, t, oldl, oldt); 4201 sendOurVisibleRect(); 4202 // update WebKit if visible title bar height changed. The logic is same 4203 // as getVisibleTitleHeight. 4204 int titleHeight = getTitleHeight(); 4205 if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { 4206 sendViewSizeZoom(); 4207 } 4208 } 4209 4210 @Override 4211 public boolean dispatchKeyEvent(KeyEvent event) { 4212 boolean dispatch = true; 4213 4214 // Textfields, plugins, and contentEditable nodes need to receive the 4215 // shift up key even if another key was released while the shift key 4216 // was held down. 4217 if (!inEditingMode() && (mNativeClass == 0 4218 || !nativePageShouldHandleShiftAndArrows())) { 4219 if (event.getAction() == KeyEvent.ACTION_DOWN) { 4220 mGotKeyDown = true; 4221 } else { 4222 if (!mGotKeyDown) { 4223 /* 4224 * We got a key up for which we were not the recipient of 4225 * the original key down. Don't give it to the view. 4226 */ 4227 dispatch = false; 4228 } 4229 mGotKeyDown = false; 4230 } 4231 } 4232 4233 if (dispatch) { 4234 return super.dispatchKeyEvent(event); 4235 } else { 4236 // We didn't dispatch, so let something else handle the key 4237 return false; 4238 } 4239 } 4240 4241 // Here are the snap align logic: 4242 // 1. If it starts nearly horizontally or vertically, snap align; 4243 // 2. If there is a dramitic direction change, let it go; 4244 // 3. If there is a same direction back and forth, lock it. 4245 4246 // adjustable parameters 4247 private int mMinLockSnapReverseDistance; 4248 private static final float MAX_SLOPE_FOR_DIAG = 1.5f; 4249 private static final int MIN_BREAK_SNAP_CROSS_DISTANCE = 80; 4250 4251 private static int sign(float x) { 4252 return x > 0 ? 1 : (x < 0 ? -1 : 0); 4253 } 4254 4255 // if the page can scroll <= this value, we won't allow the drag tracker 4256 // to have any effect. 4257 private static final int MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER = 4; 4258 4259 private class DragTrackerHandler { 4260 private final DragTracker mProxy; 4261 private final float mStartY, mStartX; 4262 private final float mMinDY, mMinDX; 4263 private final float mMaxDY, mMaxDX; 4264 private float mCurrStretchY, mCurrStretchX; 4265 private int mSX, mSY; 4266 private Interpolator mInterp; 4267 private float[] mXY = new float[2]; 4268 4269 // inner (non-state) classes can't have enums :( 4270 private static final int DRAGGING_STATE = 0; 4271 private static final int ANIMATING_STATE = 1; 4272 private static final int FINISHED_STATE = 2; 4273 private int mState; 4274 4275 public DragTrackerHandler(float x, float y, DragTracker proxy) { 4276 mProxy = proxy; 4277 4278 int docBottom = computeVerticalScrollRange() + getTitleHeight(); 4279 int viewTop = getScrollY(); 4280 int viewBottom = viewTop + getHeight(); 4281 4282 mStartY = y; 4283 mMinDY = -viewTop; 4284 mMaxDY = docBottom - viewBottom; 4285 4286 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4287 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " dragtracker y= " + y + 4288 " up/down= " + mMinDY + " " + mMaxDY); 4289 } 4290 4291 int docRight = computeHorizontalScrollRange(); 4292 int viewLeft = getScrollX(); 4293 int viewRight = viewLeft + getWidth(); 4294 mStartX = x; 4295 mMinDX = -viewLeft; 4296 mMaxDX = docRight - viewRight; 4297 4298 mState = DRAGGING_STATE; 4299 mProxy.onStartDrag(x, y); 4300 4301 // ensure we buildBitmap at least once 4302 mSX = -99999; 4303 } 4304 4305 private float computeStretch(float delta, float min, float max) { 4306 float stretch = 0; 4307 if (max - min > MIN_SCROLL_AMOUNT_TO_DISABLE_DRAG_TRACKER) { 4308 if (delta < min) { 4309 stretch = delta - min; 4310 } else if (delta > max) { 4311 stretch = delta - max; 4312 } 4313 } 4314 return stretch; 4315 } 4316 4317 public void dragTo(float x, float y) { 4318 float sy = computeStretch(mStartY - y, mMinDY, mMaxDY); 4319 float sx = computeStretch(mStartX - x, mMinDX, mMaxDX); 4320 4321 if ((mSnapScrollMode & SNAP_X) != 0) { 4322 sy = 0; 4323 } else if ((mSnapScrollMode & SNAP_Y) != 0) { 4324 sx = 0; 4325 } 4326 4327 if (mCurrStretchX != sx || mCurrStretchY != sy) { 4328 mCurrStretchX = sx; 4329 mCurrStretchY = sy; 4330 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4331 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "---- stretch " + sx + 4332 " " + sy); 4333 } 4334 if (mProxy.onStretchChange(sx, sy)) { 4335 invalidate(); 4336 } 4337 } 4338 } 4339 4340 public void stopDrag() { 4341 final int DURATION = 200; 4342 int now = (int)SystemClock.uptimeMillis(); 4343 mInterp = new Interpolator(2); 4344 mXY[0] = mCurrStretchX; 4345 mXY[1] = mCurrStretchY; 4346 // float[] blend = new float[] { 0.5f, 0, 0.75f, 1 }; 4347 float[] blend = new float[] { 0, 0.5f, 0.75f, 1 }; 4348 mInterp.setKeyFrame(0, now, mXY, blend); 4349 float[] zerozero = new float[] { 0, 0 }; 4350 mInterp.setKeyFrame(1, now + DURATION, zerozero, null); 4351 mState = ANIMATING_STATE; 4352 4353 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4354 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "----- stopDrag, starting animation"); 4355 } 4356 } 4357 4358 // Call this after each draw. If it ruturns null, the tracker is done 4359 public boolean isFinished() { 4360 return mState == FINISHED_STATE; 4361 } 4362 4363 private int hiddenHeightOfTitleBar() { 4364 return getTitleHeight() - getVisibleTitleHeight(); 4365 } 4366 4367 // need a way to know if 565 or 8888 is the right config for 4368 // capturing the display and giving it to the drag proxy 4369 private Bitmap.Config offscreenBitmapConfig() { 4370 // hard code 565 for now 4371 return Bitmap.Config.RGB_565; 4372 } 4373 4374 /* If the tracker draws, then this returns true, otherwise it will 4375 return false, and draw nothing. 4376 */ 4377 public boolean draw(Canvas canvas) { 4378 if (mCurrStretchX != 0 || mCurrStretchY != 0) { 4379 int sx = getScrollX(); 4380 int sy = getScrollY() - hiddenHeightOfTitleBar(); 4381 if (mSX != sx || mSY != sy) { 4382 buildBitmap(sx, sy); 4383 mSX = sx; 4384 mSY = sy; 4385 } 4386 4387 if (mState == ANIMATING_STATE) { 4388 Interpolator.Result result = mInterp.timeToValues(mXY); 4389 if (result == Interpolator.Result.FREEZE_END) { 4390 mState = FINISHED_STATE; 4391 return false; 4392 } else { 4393 mProxy.onStretchChange(mXY[0], mXY[1]); 4394 invalidate(); 4395 // fall through to the draw 4396 } 4397 } 4398 int count = canvas.save(Canvas.MATRIX_SAVE_FLAG); 4399 canvas.translate(sx, sy); 4400 mProxy.onDraw(canvas); 4401 canvas.restoreToCount(count); 4402 return true; 4403 } 4404 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4405 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, " -- draw false " + 4406 mCurrStretchX + " " + mCurrStretchY); 4407 } 4408 return false; 4409 } 4410 4411 private void buildBitmap(int sx, int sy) { 4412 int w = getWidth(); 4413 int h = getViewHeight(); 4414 Bitmap bm = Bitmap.createBitmap(w, h, offscreenBitmapConfig()); 4415 Canvas canvas = new Canvas(bm); 4416 canvas.translate(-sx, -sy); 4417 drawContent(canvas); 4418 4419 if (DebugFlags.DRAG_TRACKER || DEBUG_DRAG_TRACKER) { 4420 Log.d(DebugFlags.DRAG_TRACKER_LOGTAG, "--- buildBitmap " + sx + 4421 " " + sy + " " + w + " " + h); 4422 } 4423 mProxy.onBitmapChange(bm); 4424 } 4425 } 4426 4427 /** @hide */ 4428 public static class DragTracker { 4429 public void onStartDrag(float x, float y) {} 4430 public boolean onStretchChange(float sx, float sy) { 4431 // return true to have us inval the view 4432 return false; 4433 } 4434 public void onStopDrag() {} 4435 public void onBitmapChange(Bitmap bm) {} 4436 public void onDraw(Canvas canvas) {} 4437 } 4438 4439 /** @hide */ 4440 public DragTracker getDragTracker() { 4441 return mDragTracker; 4442 } 4443 4444 /** @hide */ 4445 public void setDragTracker(DragTracker tracker) { 4446 mDragTracker = tracker; 4447 } 4448 4449 private DragTracker mDragTracker; 4450 private DragTrackerHandler mDragTrackerHandler; 4451 4452 private class ScaleDetectorListener implements 4453 ScaleGestureDetector.OnScaleGestureListener { 4454 4455 public boolean onScaleBegin(ScaleGestureDetector detector) { 4456 // cancel the single touch handling 4457 cancelTouch(); 4458 mZoomManager.dismissZoomPicker(); 4459 // reset the zoom overview mode so that the page won't auto grow 4460 mZoomManager.mInZoomOverview = false; 4461 // If it is in password mode, turn it off so it does not draw 4462 // misplaced. 4463 if (inEditingMode() && nativeFocusCandidateIsPassword()) { 4464 mWebTextView.setInPassword(false); 4465 } 4466 4467 mViewManager.startZoom(); 4468 4469 return true; 4470 } 4471 4472 public void onScaleEnd(ScaleGestureDetector detector) { 4473 if (mPreviewZoomOnly) { 4474 mPreviewZoomOnly = false; 4475 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 4476 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 4477 // don't reflow when zoom in; when zoom out, do reflow if the 4478 // new scale is almost minimum scale; 4479 boolean reflowNow = (mActualScale - mZoomManager.mMinZoomScale 4480 <= MINIMUM_SCALE_INCREMENT) 4481 || ((mActualScale <= 0.8 * mTextWrapScale)); 4482 // force zoom after mPreviewZoomOnly is set to false so that the 4483 // new view size will be passed to the WebKit 4484 setNewZoomScale(mActualScale, reflowNow, true); 4485 // call invalidate() to draw without zoom filter 4486 invalidate(); 4487 } 4488 // adjust the edit text view if needed 4489 if (inEditingMode() && didUpdateTextViewBounds(false) 4490 && nativeFocusCandidateIsPassword()) { 4491 // If it is a password field, start drawing the 4492 // WebTextView once again. 4493 mWebTextView.setInPassword(true); 4494 } 4495 // start a drag, TOUCH_PINCH_DRAG, can't use TOUCH_INIT_MODE as it 4496 // may trigger the unwanted click, can't use TOUCH_DRAG_MODE as it 4497 // may trigger the unwanted fling. 4498 mTouchMode = TOUCH_PINCH_DRAG; 4499 mConfirmMove = true; 4500 startTouch(detector.getFocusX(), detector.getFocusY(), 4501 mLastTouchTime); 4502 4503 mViewManager.endZoom(); 4504 } 4505 4506 public boolean onScale(ScaleGestureDetector detector) { 4507 float scale = (float) (Math.round(detector.getScaleFactor() 4508 * mActualScale * 100) / 100.0); 4509 if (Math.abs(scale - mActualScale) >= MINIMUM_SCALE_INCREMENT) { 4510 mPreviewZoomOnly = true; 4511 // limit the scale change per step 4512 if (scale > mActualScale) { 4513 scale = Math.min(scale, mActualScale * 1.25f); 4514 } else { 4515 scale = Math.max(scale, mActualScale * 0.8f); 4516 } 4517 mZoomCenterX = detector.getFocusX(); 4518 mZoomCenterY = detector.getFocusY(); 4519 setNewZoomScale(scale, false, false); 4520 invalidate(); 4521 return true; 4522 } 4523 return false; 4524 } 4525 } 4526 4527 private boolean hitFocusedPlugin(int contentX, int contentY) { 4528 if (DebugFlags.WEB_VIEW) { 4529 Log.v(LOGTAG, "nativeFocusIsPlugin()=" + nativeFocusIsPlugin()); 4530 Rect r = nativeFocusNodeBounds(); 4531 Log.v(LOGTAG, "nativeFocusNodeBounds()=(" + r.left + ", " + r.top 4532 + ", " + r.right + ", " + r.bottom + ")"); 4533 } 4534 return nativeFocusIsPlugin() 4535 && nativeFocusNodeBounds().contains(contentX, contentY); 4536 } 4537 4538 private boolean shouldForwardTouchEvent() { 4539 return mFullScreenHolder != null || (mForwardTouchEvents 4540 && mTouchMode != TOUCH_SELECT_MODE 4541 && mPreventDefault != PREVENT_DEFAULT_IGNORE); 4542 } 4543 4544 private boolean inFullScreenMode() { 4545 return mFullScreenHolder != null; 4546 } 4547 4548 @Override 4549 public boolean onTouchEvent(MotionEvent ev) { 4550 if (mNativeClass == 0 || !isClickable() || !isLongClickable()) { 4551 return false; 4552 } 4553 4554 if (DebugFlags.WEB_VIEW) { 4555 Log.v(LOGTAG, ev + " at " + ev.getEventTime() + " mTouchMode=" 4556 + mTouchMode); 4557 } 4558 4559 int action; 4560 float x, y; 4561 long eventTime = ev.getEventTime(); 4562 4563 // FIXME: we may consider to give WebKit an option to handle multi-touch 4564 // events later. 4565 if (mSupportMultiTouch && ev.getPointerCount() > 1) { 4566 if (mZoomManager.mMinZoomScale < mZoomManager.mMaxZoomScale) { 4567 mScaleDetector.onTouchEvent(ev); 4568 if (mScaleDetector.isInProgress()) { 4569 mLastTouchTime = eventTime; 4570 return true; 4571 } 4572 x = mScaleDetector.getFocusX(); 4573 y = mScaleDetector.getFocusY(); 4574 action = ev.getAction() & MotionEvent.ACTION_MASK; 4575 if (action == MotionEvent.ACTION_POINTER_DOWN) { 4576 cancelTouch(); 4577 action = MotionEvent.ACTION_DOWN; 4578 } else if (action == MotionEvent.ACTION_POINTER_UP) { 4579 // set mLastTouchX/Y to the remaining point 4580 mLastTouchX = x; 4581 mLastTouchY = y; 4582 } else if (action == MotionEvent.ACTION_MOVE) { 4583 // negative x or y indicate it is on the edge, skip it. 4584 if (x < 0 || y < 0) { 4585 return true; 4586 } 4587 } 4588 } else { 4589 // if the page disallow zoom, skip multi-pointer action 4590 return true; 4591 } 4592 } else { 4593 action = ev.getAction(); 4594 x = ev.getX(); 4595 y = ev.getY(); 4596 } 4597 4598 // Due to the touch screen edge effect, a touch closer to the edge 4599 // always snapped to the edge. As getViewWidth() can be different from 4600 // getWidth() due to the scrollbar, adjusting the point to match 4601 // getViewWidth(). Same applied to the height. 4602 if (x > getViewWidth() - 1) { 4603 x = getViewWidth() - 1; 4604 } 4605 if (y > getViewHeightWithTitle() - 1) { 4606 y = getViewHeightWithTitle() - 1; 4607 } 4608 4609 float fDeltaX = mLastTouchX - x; 4610 float fDeltaY = mLastTouchY - y; 4611 int deltaX = (int) fDeltaX; 4612 int deltaY = (int) fDeltaY; 4613 int contentX = viewToContentX((int) x + mScrollX); 4614 int contentY = viewToContentY((int) y + mScrollY); 4615 4616 switch (action) { 4617 case MotionEvent.ACTION_DOWN: { 4618 mPreventDefault = PREVENT_DEFAULT_NO; 4619 mConfirmMove = false; 4620 if (!mScroller.isFinished()) { 4621 // stop the current scroll animation, but if this is 4622 // the start of a fling, allow it to add to the current 4623 // fling's velocity 4624 mScroller.abortAnimation(); 4625 mTouchMode = TOUCH_DRAG_START_MODE; 4626 mConfirmMove = true; 4627 mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); 4628 } else if (!inFullScreenMode() && mShiftIsPressed) { 4629 mSelectX = mScrollX + (int) x; 4630 mSelectY = mScrollY + (int) y; 4631 mTouchMode = TOUCH_SELECT_MODE; 4632 if (DebugFlags.WEB_VIEW) { 4633 Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY); 4634 } 4635 nativeMoveSelection(contentX, contentY, false); 4636 mTouchSelection = mExtendSelection = true; 4637 invalidate(); // draw the i-beam instead of the arrow 4638 } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { 4639 mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); 4640 if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { 4641 mTouchMode = TOUCH_DOUBLE_TAP_MODE; 4642 } else { 4643 // commit the short press action for the previous tap 4644 doShortPress(); 4645 mTouchMode = TOUCH_INIT_MODE; 4646 mDeferTouchProcess = (!inFullScreenMode() 4647 && mForwardTouchEvents) ? hitFocusedPlugin( 4648 contentX, contentY) : false; 4649 } 4650 } else { // the normal case 4651 mPreviewZoomOnly = false; 4652 mTouchMode = TOUCH_INIT_MODE; 4653 mDeferTouchProcess = (!inFullScreenMode() 4654 && mForwardTouchEvents) ? hitFocusedPlugin( 4655 contentX, contentY) : false; 4656 mWebViewCore.sendMessage( 4657 EventHub.UPDATE_FRAME_CACHE_IF_LOADING); 4658 if (mLogEvent && eventTime - mLastTouchUpTime < 1000) { 4659 EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION, 4660 (eventTime - mLastTouchUpTime), eventTime); 4661 } 4662 } 4663 // Trigger the link 4664 if (mTouchMode == TOUCH_INIT_MODE 4665 || mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 4666 mPrivateHandler.sendEmptyMessageDelayed( 4667 SWITCH_TO_SHORTPRESS, TAP_TIMEOUT); 4668 mPrivateHandler.sendEmptyMessageDelayed( 4669 SWITCH_TO_LONGPRESS, LONG_PRESS_TIMEOUT); 4670 if (inFullScreenMode() || mDeferTouchProcess) { 4671 mPreventDefault = PREVENT_DEFAULT_YES; 4672 } else if (mForwardTouchEvents) { 4673 mPreventDefault = PREVENT_DEFAULT_MAYBE_YES; 4674 } else { 4675 mPreventDefault = PREVENT_DEFAULT_NO; 4676 } 4677 // pass the touch events from UI thread to WebCore thread 4678 if (shouldForwardTouchEvent()) { 4679 TouchEventData ted = new TouchEventData(); 4680 ted.mAction = action; 4681 ted.mX = contentX; 4682 ted.mY = contentY; 4683 ted.mMetaState = ev.getMetaState(); 4684 ted.mReprocess = mDeferTouchProcess; 4685 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 4686 if (mDeferTouchProcess) { 4687 // still needs to set them for compute deltaX/Y 4688 mLastTouchX = x; 4689 mLastTouchY = y; 4690 break; 4691 } 4692 if (!inFullScreenMode()) { 4693 mPrivateHandler.sendMessageDelayed(mPrivateHandler 4694 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 4695 action, 0), TAP_TIMEOUT); 4696 } 4697 } 4698 } 4699 startTouch(x, y, eventTime); 4700 break; 4701 } 4702 case MotionEvent.ACTION_MOVE: { 4703 boolean firstMove = false; 4704 if (!mConfirmMove && (deltaX * deltaX + deltaY * deltaY) 4705 >= mTouchSlopSquare) { 4706 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 4707 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 4708 mConfirmMove = true; 4709 firstMove = true; 4710 if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 4711 mTouchMode = TOUCH_INIT_MODE; 4712 } 4713 } 4714 // pass the touch events from UI thread to WebCore thread 4715 if (shouldForwardTouchEvent() && mConfirmMove && (firstMove 4716 || eventTime - mLastSentTouchTime > mCurrentTouchInterval)) { 4717 TouchEventData ted = new TouchEventData(); 4718 ted.mAction = action; 4719 ted.mX = contentX; 4720 ted.mY = contentY; 4721 ted.mMetaState = ev.getMetaState(); 4722 ted.mReprocess = mDeferTouchProcess; 4723 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 4724 mLastSentTouchTime = eventTime; 4725 if (mDeferTouchProcess) { 4726 break; 4727 } 4728 if (firstMove && !inFullScreenMode()) { 4729 mPrivateHandler.sendMessageDelayed(mPrivateHandler 4730 .obtainMessage(PREVENT_DEFAULT_TIMEOUT, 4731 action, 0), TAP_TIMEOUT); 4732 } 4733 } 4734 if (mTouchMode == TOUCH_DONE_MODE 4735 || mPreventDefault == PREVENT_DEFAULT_YES) { 4736 // no dragging during scroll zoom animation, or when prevent 4737 // default is yes 4738 break; 4739 } 4740 if (mVelocityTracker == null) { 4741 Log.e(LOGTAG, "Got null mVelocityTracker when " 4742 + "mPreventDefault = " + mPreventDefault 4743 + " mDeferTouchProcess = " + mDeferTouchProcess 4744 + " mTouchMode = " + mTouchMode); 4745 } 4746 mVelocityTracker.addMovement(ev); 4747 if (mTouchMode != TOUCH_DRAG_MODE) { 4748 if (mTouchMode == TOUCH_SELECT_MODE) { 4749 mSelectX = mScrollX + (int) x; 4750 mSelectY = mScrollY + (int) y; 4751 if (DebugFlags.WEB_VIEW) { 4752 Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY); 4753 } 4754 nativeMoveSelection(contentX, contentY, true); 4755 invalidate(); 4756 break; 4757 } 4758 4759 if (!mConfirmMove) { 4760 break; 4761 } 4762 4763 if (mPreventDefault == PREVENT_DEFAULT_MAYBE_YES 4764 || mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 4765 // track mLastTouchTime as we may need to do fling at 4766 // ACTION_UP 4767 mLastTouchTime = eventTime; 4768 break; 4769 } 4770 // if it starts nearly horizontal or vertical, enforce it 4771 int ax = Math.abs(deltaX); 4772 int ay = Math.abs(deltaY); 4773 if (ax > MAX_SLOPE_FOR_DIAG * ay) { 4774 mSnapScrollMode = SNAP_X; 4775 mSnapPositive = deltaX > 0; 4776 } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { 4777 mSnapScrollMode = SNAP_Y; 4778 mSnapPositive = deltaY > 0; 4779 } 4780 4781 mTouchMode = TOUCH_DRAG_MODE; 4782 mLastTouchX = x; 4783 mLastTouchY = y; 4784 fDeltaX = 0.0f; 4785 fDeltaY = 0.0f; 4786 deltaX = 0; 4787 deltaY = 0; 4788 4789 startDrag(); 4790 } 4791 4792 if (mDragTrackerHandler != null) { 4793 mDragTrackerHandler.dragTo(x, y); 4794 } 4795 4796 // do pan 4797 int newScrollX = pinLocX(mScrollX + deltaX); 4798 int newDeltaX = newScrollX - mScrollX; 4799 if (deltaX != newDeltaX) { 4800 deltaX = newDeltaX; 4801 fDeltaX = (float) newDeltaX; 4802 } 4803 int newScrollY = pinLocY(mScrollY + deltaY); 4804 int newDeltaY = newScrollY - mScrollY; 4805 if (deltaY != newDeltaY) { 4806 deltaY = newDeltaY; 4807 fDeltaY = (float) newDeltaY; 4808 } 4809 boolean done = false; 4810 boolean keepScrollBarsVisible = false; 4811 if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) { 4812 mLastTouchX = x; 4813 mLastTouchY = y; 4814 keepScrollBarsVisible = done = true; 4815 } else { 4816 if (mSnapScrollMode == SNAP_X || mSnapScrollMode == SNAP_Y) { 4817 int ax = Math.abs(deltaX); 4818 int ay = Math.abs(deltaY); 4819 if (mSnapScrollMode == SNAP_X) { 4820 // radical change means getting out of snap mode 4821 if (ay > MAX_SLOPE_FOR_DIAG * ax 4822 && ay > MIN_BREAK_SNAP_CROSS_DISTANCE) { 4823 mSnapScrollMode = SNAP_NONE; 4824 } 4825 // reverse direction means lock in the snap mode 4826 if (ax > MAX_SLOPE_FOR_DIAG * ay && 4827 (mSnapPositive 4828 ? deltaX < -mMinLockSnapReverseDistance 4829 : deltaX > mMinLockSnapReverseDistance)) { 4830 mSnapScrollMode |= SNAP_LOCK; 4831 } 4832 } else { 4833 // radical change means getting out of snap mode 4834 if (ax > MAX_SLOPE_FOR_DIAG * ay 4835 && ax > MIN_BREAK_SNAP_CROSS_DISTANCE) { 4836 mSnapScrollMode = SNAP_NONE; 4837 } 4838 // reverse direction means lock in the snap mode 4839 if (ay > MAX_SLOPE_FOR_DIAG * ax && 4840 (mSnapPositive 4841 ? deltaY < -mMinLockSnapReverseDistance 4842 : deltaY > mMinLockSnapReverseDistance)) { 4843 mSnapScrollMode |= SNAP_LOCK; 4844 } 4845 } 4846 } 4847 if (mSnapScrollMode != SNAP_NONE) { 4848 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 4849 deltaY = 0; 4850 } else { 4851 deltaX = 0; 4852 } 4853 } 4854 if ((deltaX | deltaY) != 0) { 4855 if (deltaX != 0) { 4856 mLastTouchX = x; 4857 } 4858 if (deltaY != 0) { 4859 mLastTouchY = y; 4860 } 4861 mHeldMotionless = MOTIONLESS_FALSE; 4862 } else { 4863 // keep the scrollbar on the screen even there is no 4864 // scroll 4865 mLastTouchX = x; 4866 mLastTouchY = y; 4867 keepScrollBarsVisible = true; 4868 } 4869 mLastTouchTime = eventTime; 4870 mUserScroll = true; 4871 } 4872 4873 doDrag(deltaX, deltaY); 4874 4875 if (keepScrollBarsVisible) { 4876 if (mHeldMotionless != MOTIONLESS_TRUE) { 4877 mHeldMotionless = MOTIONLESS_TRUE; 4878 invalidate(); 4879 } 4880 // keep the scrollbar on the screen even there is no scroll 4881 awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), 4882 false); 4883 // return false to indicate that we can't pan out of the 4884 // view space 4885 return !done; 4886 } 4887 break; 4888 } 4889 case MotionEvent.ACTION_UP: { 4890 if (!isFocused()) requestFocus(); 4891 // pass the touch events from UI thread to WebCore thread 4892 if (shouldForwardTouchEvent()) { 4893 TouchEventData ted = new TouchEventData(); 4894 ted.mAction = action; 4895 ted.mX = contentX; 4896 ted.mY = contentY; 4897 ted.mMetaState = ev.getMetaState(); 4898 ted.mReprocess = mDeferTouchProcess; 4899 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 4900 } 4901 mLastTouchUpTime = eventTime; 4902 switch (mTouchMode) { 4903 case TOUCH_DOUBLE_TAP_MODE: // double tap 4904 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 4905 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 4906 if (inFullScreenMode() || mDeferTouchProcess) { 4907 TouchEventData ted = new TouchEventData(); 4908 ted.mAction = WebViewCore.ACTION_DOUBLETAP; 4909 ted.mX = contentX; 4910 ted.mY = contentY; 4911 ted.mMetaState = ev.getMetaState(); 4912 ted.mReprocess = mDeferTouchProcess; 4913 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 4914 } else if (mPreventDefault != PREVENT_DEFAULT_YES){ 4915 doDoubleTap(); 4916 mTouchMode = TOUCH_DONE_MODE; 4917 } 4918 break; 4919 case TOUCH_SELECT_MODE: 4920 commitCopy(); 4921 mTouchSelection = false; 4922 break; 4923 case TOUCH_INIT_MODE: // tap 4924 case TOUCH_SHORTPRESS_START_MODE: 4925 case TOUCH_SHORTPRESS_MODE: 4926 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 4927 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 4928 if (mConfirmMove) { 4929 Log.w(LOGTAG, "Miss a drag as we are waiting for" + 4930 " WebCore's response for touch down."); 4931 if (mPreventDefault != PREVENT_DEFAULT_YES 4932 && (computeMaxScrollX() > 0 4933 || computeMaxScrollY() > 0)) { 4934 // If the user has performed a very quick touch 4935 // sequence it is possible that we may get here 4936 // before WebCore has had a chance to process the events. 4937 // In this case, any call to preventDefault in the 4938 // JS touch handler will not have been executed yet. 4939 // Hence we will see both the UI (now) and WebCore 4940 // (when context switches) handling the event, 4941 // regardless of whether the web developer actually 4942 // doeses preventDefault in their touch handler. This 4943 // is the nature of our asynchronous touch model. 4944 4945 // we will not rewrite drag code here, but we 4946 // will try fling if it applies. 4947 WebViewCore.reducePriority(); 4948 // to get better performance, pause updating the 4949 // picture 4950 WebViewCore.pauseUpdatePicture(mWebViewCore); 4951 // fall through to TOUCH_DRAG_MODE 4952 } else { 4953 // WebKit may consume the touch event and modify 4954 // DOM. drawContentPicture() will be called with 4955 // animateSroll as true for better performance. 4956 // Force redraw in high-quality. 4957 invalidate(); 4958 break; 4959 } 4960 } else { 4961 if (mTouchMode == TOUCH_INIT_MODE) { 4962 mPrivateHandler.sendEmptyMessageDelayed( 4963 RELEASE_SINGLE_TAP, ViewConfiguration 4964 .getDoubleTapTimeout()); 4965 } else { 4966 doShortPress(); 4967 } 4968 break; 4969 } 4970 case TOUCH_DRAG_MODE: 4971 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 4972 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 4973 // if the user waits a while w/o moving before the 4974 // up, we don't want to do a fling 4975 if (eventTime - mLastTouchTime <= MIN_FLING_TIME) { 4976 if (mVelocityTracker == null) { 4977 Log.e(LOGTAG, "Got null mVelocityTracker when " 4978 + "mPreventDefault = " 4979 + mPreventDefault 4980 + " mDeferTouchProcess = " 4981 + mDeferTouchProcess); 4982 } 4983 mVelocityTracker.addMovement(ev); 4984 // set to MOTIONLESS_IGNORE so that it won't keep 4985 // removing and sending message in 4986 // drawCoreAndCursorRing() 4987 mHeldMotionless = MOTIONLESS_IGNORE; 4988 doFling(); 4989 break; 4990 } 4991 // redraw in high-quality, as we're done dragging 4992 mHeldMotionless = MOTIONLESS_TRUE; 4993 invalidate(); 4994 // fall through 4995 case TOUCH_DRAG_START_MODE: 4996 // TOUCH_DRAG_START_MODE should not happen for the real 4997 // device as we almost certain will get a MOVE. But this 4998 // is possible on emulator. 4999 mLastVelocity = 0; 5000 WebViewCore.resumePriority(); 5001 WebViewCore.resumeUpdatePicture(mWebViewCore); 5002 break; 5003 } 5004 stopTouch(); 5005 break; 5006 } 5007 case MotionEvent.ACTION_CANCEL: { 5008 if (mTouchMode == TOUCH_DRAG_MODE) { 5009 invalidate(); 5010 } 5011 cancelWebCoreTouchEvent(contentX, contentY, false); 5012 cancelTouch(); 5013 break; 5014 } 5015 } 5016 return true; 5017 } 5018 5019 private void cancelWebCoreTouchEvent(int x, int y, boolean removeEvents) { 5020 if (shouldForwardTouchEvent()) { 5021 if (removeEvents) { 5022 mWebViewCore.removeMessages(EventHub.TOUCH_EVENT); 5023 } 5024 TouchEventData ted = new TouchEventData(); 5025 ted.mX = x; 5026 ted.mY = y; 5027 ted.mAction = MotionEvent.ACTION_CANCEL; 5028 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 5029 mPreventDefault = PREVENT_DEFAULT_IGNORE; 5030 } 5031 } 5032 5033 private void startTouch(float x, float y, long eventTime) { 5034 // Remember where the motion event started 5035 mLastTouchX = x; 5036 mLastTouchY = y; 5037 mLastTouchTime = eventTime; 5038 mVelocityTracker = VelocityTracker.obtain(); 5039 mSnapScrollMode = SNAP_NONE; 5040 if (mDragTracker != null) { 5041 mDragTrackerHandler = new DragTrackerHandler(x, y, mDragTracker); 5042 } 5043 } 5044 5045 private void startDrag() { 5046 WebViewCore.reducePriority(); 5047 // to get better performance, pause updating the picture 5048 WebViewCore.pauseUpdatePicture(mWebViewCore); 5049 if (!mDragFromTextInput) { 5050 nativeHideCursor(); 5051 } 5052 5053 if (mHorizontalScrollBarMode != SCROLLBAR_ALWAYSOFF 5054 || mVerticalScrollBarMode != SCROLLBAR_ALWAYSOFF) { 5055 mZoomManager.invokeZoomPicker(); 5056 } 5057 } 5058 5059 private void doDrag(int deltaX, int deltaY) { 5060 if ((deltaX | deltaY) != 0) { 5061 scrollBy(deltaX, deltaY); 5062 } 5063 mZoomManager.keepZoomPickerVisible(); 5064 } 5065 5066 private void stopTouch() { 5067 if (mDragTrackerHandler != null) { 5068 mDragTrackerHandler.stopDrag(); 5069 } 5070 // we also use mVelocityTracker == null to tell us that we are 5071 // not "moving around", so we can take the slower/prettier 5072 // mode in the drawing code 5073 if (mVelocityTracker != null) { 5074 mVelocityTracker.recycle(); 5075 mVelocityTracker = null; 5076 } 5077 } 5078 5079 private void cancelTouch() { 5080 if (mDragTrackerHandler != null) { 5081 mDragTrackerHandler.stopDrag(); 5082 } 5083 // we also use mVelocityTracker == null to tell us that we are 5084 // not "moving around", so we can take the slower/prettier 5085 // mode in the drawing code 5086 if (mVelocityTracker != null) { 5087 mVelocityTracker.recycle(); 5088 mVelocityTracker = null; 5089 } 5090 if (mTouchMode == TOUCH_DRAG_MODE) { 5091 WebViewCore.resumePriority(); 5092 WebViewCore.resumeUpdatePicture(mWebViewCore); 5093 } 5094 mPrivateHandler.removeMessages(SWITCH_TO_SHORTPRESS); 5095 mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); 5096 mPrivateHandler.removeMessages(DRAG_HELD_MOTIONLESS); 5097 mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); 5098 mHeldMotionless = MOTIONLESS_TRUE; 5099 mTouchMode = TOUCH_DONE_MODE; 5100 nativeHideCursor(); 5101 } 5102 5103 private long mTrackballFirstTime = 0; 5104 private long mTrackballLastTime = 0; 5105 private float mTrackballRemainsX = 0.0f; 5106 private float mTrackballRemainsY = 0.0f; 5107 private int mTrackballXMove = 0; 5108 private int mTrackballYMove = 0; 5109 private boolean mExtendSelection = false; 5110 private boolean mTouchSelection = false; 5111 private static final int TRACKBALL_KEY_TIMEOUT = 1000; 5112 private static final int TRACKBALL_TIMEOUT = 200; 5113 private static final int TRACKBALL_WAIT = 100; 5114 private static final int TRACKBALL_SCALE = 400; 5115 private static final int TRACKBALL_SCROLL_COUNT = 5; 5116 private static final int TRACKBALL_MOVE_COUNT = 10; 5117 private static final int TRACKBALL_MULTIPLIER = 3; 5118 private static final int SELECT_CURSOR_OFFSET = 16; 5119 private int mSelectX = 0; 5120 private int mSelectY = 0; 5121 private boolean mFocusSizeChanged = false; 5122 private boolean mShiftIsPressed = false; 5123 private boolean mTrackballDown = false; 5124 private long mTrackballUpTime = 0; 5125 private long mLastCursorTime = 0; 5126 private Rect mLastCursorBounds; 5127 5128 // Set by default; BrowserActivity clears to interpret trackball data 5129 // directly for movement. Currently, the framework only passes 5130 // arrow key events, not trackball events, from one child to the next 5131 private boolean mMapTrackballToArrowKeys = true; 5132 5133 public void setMapTrackballToArrowKeys(boolean setMap) { 5134 mMapTrackballToArrowKeys = setMap; 5135 } 5136 5137 void resetTrackballTime() { 5138 mTrackballLastTime = 0; 5139 } 5140 5141 @Override 5142 public boolean onTrackballEvent(MotionEvent ev) { 5143 long time = ev.getEventTime(); 5144 if ((ev.getMetaState() & KeyEvent.META_ALT_ON) != 0) { 5145 if (ev.getY() > 0) pageDown(true); 5146 if (ev.getY() < 0) pageUp(true); 5147 return true; 5148 } 5149 boolean shiftPressed = mShiftIsPressed && (mNativeClass == 0 5150 || !nativePageShouldHandleShiftAndArrows()); 5151 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 5152 if (shiftPressed) { 5153 return true; // discard press if copy in progress 5154 } 5155 mTrackballDown = true; 5156 if (mNativeClass == 0) { 5157 return false; 5158 } 5159 nativeRecordButtons(hasFocus() && hasWindowFocus(), true, true); 5160 if (time - mLastCursorTime <= TRACKBALL_TIMEOUT 5161 && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { 5162 nativeSelectBestAt(mLastCursorBounds); 5163 } 5164 if (DebugFlags.WEB_VIEW) { 5165 Log.v(LOGTAG, "onTrackballEvent down ev=" + ev 5166 + " time=" + time 5167 + " mLastCursorTime=" + mLastCursorTime); 5168 } 5169 if (isInTouchMode()) requestFocusFromTouch(); 5170 return false; // let common code in onKeyDown at it 5171 } 5172 if (ev.getAction() == MotionEvent.ACTION_UP) { 5173 // LONG_PRESS_CENTER is set in common onKeyDown 5174 mPrivateHandler.removeMessages(LONG_PRESS_CENTER); 5175 mTrackballDown = false; 5176 mTrackballUpTime = time; 5177 if (shiftPressed) { 5178 if (mExtendSelection) { 5179 commitCopy(); 5180 } else { 5181 mExtendSelection = true; 5182 invalidate(); // draw the i-beam instead of the arrow 5183 } 5184 return true; // discard press if copy in progress 5185 } 5186 if (DebugFlags.WEB_VIEW) { 5187 Log.v(LOGTAG, "onTrackballEvent up ev=" + ev 5188 + " time=" + time 5189 ); 5190 } 5191 return false; // let common code in onKeyUp at it 5192 } 5193 if (mMapTrackballToArrowKeys && mShiftIsPressed == false) { 5194 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent gmail quit"); 5195 return false; 5196 } 5197 if (mTrackballDown) { 5198 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent down quit"); 5199 return true; // discard move if trackball is down 5200 } 5201 if (time - mTrackballUpTime < TRACKBALL_TIMEOUT) { 5202 if (DebugFlags.WEB_VIEW) Log.v(LOGTAG, "onTrackballEvent up timeout quit"); 5203 return true; 5204 } 5205 // TODO: alternatively we can do panning as touch does 5206 switchOutDrawHistory(); 5207 if (time - mTrackballLastTime > TRACKBALL_TIMEOUT) { 5208 if (DebugFlags.WEB_VIEW) { 5209 Log.v(LOGTAG, "onTrackballEvent time=" 5210 + time + " last=" + mTrackballLastTime); 5211 } 5212 mTrackballFirstTime = time; 5213 mTrackballXMove = mTrackballYMove = 0; 5214 } 5215 mTrackballLastTime = time; 5216 if (DebugFlags.WEB_VIEW) { 5217 Log.v(LOGTAG, "onTrackballEvent ev=" + ev + " time=" + time); 5218 } 5219 mTrackballRemainsX += ev.getX(); 5220 mTrackballRemainsY += ev.getY(); 5221 doTrackball(time); 5222 return true; 5223 } 5224 5225 void moveSelection(float xRate, float yRate) { 5226 if (mNativeClass == 0) 5227 return; 5228 int width = getViewWidth(); 5229 int height = getViewHeight(); 5230 mSelectX += xRate; 5231 mSelectY += yRate; 5232 int maxX = width + mScrollX; 5233 int maxY = height + mScrollY; 5234 mSelectX = Math.min(maxX, Math.max(mScrollX - SELECT_CURSOR_OFFSET 5235 , mSelectX)); 5236 mSelectY = Math.min(maxY, Math.max(mScrollY - SELECT_CURSOR_OFFSET 5237 , mSelectY)); 5238 if (DebugFlags.WEB_VIEW) { 5239 Log.v(LOGTAG, "moveSelection" 5240 + " mSelectX=" + mSelectX 5241 + " mSelectY=" + mSelectY 5242 + " mScrollX=" + mScrollX 5243 + " mScrollY=" + mScrollY 5244 + " xRate=" + xRate 5245 + " yRate=" + yRate 5246 ); 5247 } 5248 nativeMoveSelection(viewToContentX(mSelectX), 5249 viewToContentY(mSelectY), mExtendSelection); 5250 int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET 5251 : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 5252 : 0; 5253 int scrollY = mSelectY < mScrollY ? -SELECT_CURSOR_OFFSET 5254 : mSelectY > maxY - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET 5255 : 0; 5256 pinScrollBy(scrollX, scrollY, true, 0); 5257 Rect select = new Rect(mSelectX, mSelectY, mSelectX + 1, mSelectY + 1); 5258 requestRectangleOnScreen(select); 5259 invalidate(); 5260 } 5261 5262 private int scaleTrackballX(float xRate, int width) { 5263 int xMove = (int) (xRate / TRACKBALL_SCALE * width); 5264 int nextXMove = xMove; 5265 if (xMove > 0) { 5266 if (xMove > mTrackballXMove) { 5267 xMove -= mTrackballXMove; 5268 } 5269 } else if (xMove < mTrackballXMove) { 5270 xMove -= mTrackballXMove; 5271 } 5272 mTrackballXMove = nextXMove; 5273 return xMove; 5274 } 5275 5276 private int scaleTrackballY(float yRate, int height) { 5277 int yMove = (int) (yRate / TRACKBALL_SCALE * height); 5278 int nextYMove = yMove; 5279 if (yMove > 0) { 5280 if (yMove > mTrackballYMove) { 5281 yMove -= mTrackballYMove; 5282 } 5283 } else if (yMove < mTrackballYMove) { 5284 yMove -= mTrackballYMove; 5285 } 5286 mTrackballYMove = nextYMove; 5287 return yMove; 5288 } 5289 5290 private int keyCodeToSoundsEffect(int keyCode) { 5291 switch(keyCode) { 5292 case KeyEvent.KEYCODE_DPAD_UP: 5293 return SoundEffectConstants.NAVIGATION_UP; 5294 case KeyEvent.KEYCODE_DPAD_RIGHT: 5295 return SoundEffectConstants.NAVIGATION_RIGHT; 5296 case KeyEvent.KEYCODE_DPAD_DOWN: 5297 return SoundEffectConstants.NAVIGATION_DOWN; 5298 case KeyEvent.KEYCODE_DPAD_LEFT: 5299 return SoundEffectConstants.NAVIGATION_LEFT; 5300 } 5301 throw new IllegalArgumentException("keyCode must be one of " + 5302 "{KEYCODE_DPAD_UP, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_DOWN, " + 5303 "KEYCODE_DPAD_LEFT}."); 5304 } 5305 5306 private void doTrackball(long time) { 5307 int elapsed = (int) (mTrackballLastTime - mTrackballFirstTime); 5308 if (elapsed == 0) { 5309 elapsed = TRACKBALL_TIMEOUT; 5310 } 5311 float xRate = mTrackballRemainsX * 1000 / elapsed; 5312 float yRate = mTrackballRemainsY * 1000 / elapsed; 5313 int viewWidth = getViewWidth(); 5314 int viewHeight = getViewHeight(); 5315 if (mShiftIsPressed && (mNativeClass == 0 5316 || !nativePageShouldHandleShiftAndArrows())) { 5317 moveSelection(scaleTrackballX(xRate, viewWidth), 5318 scaleTrackballY(yRate, viewHeight)); 5319 mTrackballRemainsX = mTrackballRemainsY = 0; 5320 return; 5321 } 5322 float ax = Math.abs(xRate); 5323 float ay = Math.abs(yRate); 5324 float maxA = Math.max(ax, ay); 5325 if (DebugFlags.WEB_VIEW) { 5326 Log.v(LOGTAG, "doTrackball elapsed=" + elapsed 5327 + " xRate=" + xRate 5328 + " yRate=" + yRate 5329 + " mTrackballRemainsX=" + mTrackballRemainsX 5330 + " mTrackballRemainsY=" + mTrackballRemainsY); 5331 } 5332 int width = mContentWidth - viewWidth; 5333 int height = mContentHeight - viewHeight; 5334 if (width < 0) width = 0; 5335 if (height < 0) height = 0; 5336 ax = Math.abs(mTrackballRemainsX * TRACKBALL_MULTIPLIER); 5337 ay = Math.abs(mTrackballRemainsY * TRACKBALL_MULTIPLIER); 5338 maxA = Math.max(ax, ay); 5339 int count = Math.max(0, (int) maxA); 5340 int oldScrollX = mScrollX; 5341 int oldScrollY = mScrollY; 5342 if (count > 0) { 5343 int selectKeyCode = ax < ay ? mTrackballRemainsY < 0 ? 5344 KeyEvent.KEYCODE_DPAD_UP : KeyEvent.KEYCODE_DPAD_DOWN : 5345 mTrackballRemainsX < 0 ? KeyEvent.KEYCODE_DPAD_LEFT : 5346 KeyEvent.KEYCODE_DPAD_RIGHT; 5347 count = Math.min(count, TRACKBALL_MOVE_COUNT); 5348 if (DebugFlags.WEB_VIEW) { 5349 Log.v(LOGTAG, "doTrackball keyCode=" + selectKeyCode 5350 + " count=" + count 5351 + " mTrackballRemainsX=" + mTrackballRemainsX 5352 + " mTrackballRemainsY=" + mTrackballRemainsY); 5353 } 5354 if (mNativeClass != 0 && nativePageShouldHandleShiftAndArrows()) { 5355 for (int i = 0; i < count; i++) { 5356 letPageHandleNavKey(selectKeyCode, time, true); 5357 } 5358 letPageHandleNavKey(selectKeyCode, time, false); 5359 } else if (navHandledKey(selectKeyCode, count, false, time)) { 5360 playSoundEffect(keyCodeToSoundsEffect(selectKeyCode)); 5361 } 5362 mTrackballRemainsX = mTrackballRemainsY = 0; 5363 } 5364 if (count >= TRACKBALL_SCROLL_COUNT) { 5365 int xMove = scaleTrackballX(xRate, width); 5366 int yMove = scaleTrackballY(yRate, height); 5367 if (DebugFlags.WEB_VIEW) { 5368 Log.v(LOGTAG, "doTrackball pinScrollBy" 5369 + " count=" + count 5370 + " xMove=" + xMove + " yMove=" + yMove 5371 + " mScrollX-oldScrollX=" + (mScrollX-oldScrollX) 5372 + " mScrollY-oldScrollY=" + (mScrollY-oldScrollY) 5373 ); 5374 } 5375 if (Math.abs(mScrollX - oldScrollX) > Math.abs(xMove)) { 5376 xMove = 0; 5377 } 5378 if (Math.abs(mScrollY - oldScrollY) > Math.abs(yMove)) { 5379 yMove = 0; 5380 } 5381 if (xMove != 0 || yMove != 0) { 5382 pinScrollBy(xMove, yMove, true, 0); 5383 } 5384 mUserScroll = true; 5385 } 5386 } 5387 5388 private int computeMaxScrollX() { 5389 return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); 5390 } 5391 5392 private int computeMaxScrollY() { 5393 return Math.max(computeVerticalScrollRange() + getTitleHeight() 5394 - getViewHeightWithTitle(), 0); 5395 } 5396 5397 public void flingScroll(int vx, int vy) { 5398 mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0, 5399 computeMaxScrollY()); 5400 invalidate(); 5401 } 5402 5403 private void doFling() { 5404 if (mVelocityTracker == null) { 5405 return; 5406 } 5407 int maxX = computeMaxScrollX(); 5408 int maxY = computeMaxScrollY(); 5409 5410 mVelocityTracker.computeCurrentVelocity(1000, mMaximumFling); 5411 int vx = (int) mVelocityTracker.getXVelocity(); 5412 int vy = (int) mVelocityTracker.getYVelocity(); 5413 5414 if (mSnapScrollMode != SNAP_NONE) { 5415 if ((mSnapScrollMode & SNAP_X) == SNAP_X) { 5416 vy = 0; 5417 } else { 5418 vx = 0; 5419 } 5420 } 5421 if (true /* EMG release: make our fling more like Maps' */) { 5422 // maps cuts their velocity in half 5423 vx = vx * 3 / 4; 5424 vy = vy * 3 / 4; 5425 } 5426 if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { 5427 WebViewCore.resumePriority(); 5428 WebViewCore.resumeUpdatePicture(mWebViewCore); 5429 return; 5430 } 5431 float currentVelocity = mScroller.getCurrVelocity(); 5432 if (mLastVelocity > 0 && currentVelocity > 0) { 5433 float deltaR = (float) (Math.abs(Math.atan2(mLastVelY, mLastVelX) 5434 - Math.atan2(vy, vx))); 5435 final float circle = (float) (Math.PI) * 2.0f; 5436 if (deltaR > circle * 0.9f || deltaR < circle * 0.1f) { 5437 vx += currentVelocity * mLastVelX / mLastVelocity; 5438 vy += currentVelocity * mLastVelY / mLastVelocity; 5439 if (DebugFlags.WEB_VIEW) { 5440 Log.v(LOGTAG, "doFling vx= " + vx + " vy=" + vy); 5441 } 5442 } else if (DebugFlags.WEB_VIEW) { 5443 Log.v(LOGTAG, "doFling missed " + deltaR / circle); 5444 } 5445 } else if (DebugFlags.WEB_VIEW) { 5446 Log.v(LOGTAG, "doFling start last=" + mLastVelocity 5447 + " current=" + currentVelocity 5448 + " vx=" + vx + " vy=" + vy 5449 + " maxX=" + maxX + " maxY=" + maxY 5450 + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY); 5451 } 5452 mLastVelX = vx; 5453 mLastVelY = vy; 5454 mLastVelocity = (float) Math.hypot(vx, vy); 5455 5456 mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY); 5457 // TODO: duration is calculated based on velocity, if the range is 5458 // small, the animation will stop before duration is up. We may 5459 // want to calculate how long the animation is going to run to precisely 5460 // resume the webcore update. 5461 final int time = mScroller.getDuration(); 5462 mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time); 5463 awakenScrollBars(time); 5464 invalidate(); 5465 } 5466 5467 private boolean zoomWithPreview(float scale, boolean updateTextWrapScale) { 5468 float oldScale = mActualScale; 5469 mInitialScrollX = mScrollX; 5470 mInitialScrollY = mScrollY; 5471 5472 // snap to DEFAULT_SCALE if it is close 5473 if (Math.abs(scale - mDefaultScale) < MINIMUM_SCALE_INCREMENT) { 5474 scale = mDefaultScale; 5475 } 5476 5477 setNewZoomScale(scale, updateTextWrapScale, false); 5478 5479 if (oldScale != mActualScale) { 5480 // use mZoomPickerScale to see zoom preview first 5481 mZoomStart = SystemClock.uptimeMillis(); 5482 mInvInitialZoomScale = 1.0f / oldScale; 5483 mInvFinalZoomScale = 1.0f / mActualScale; 5484 mZoomScale = mActualScale; 5485 WebViewCore.pauseUpdatePicture(mWebViewCore); 5486 invalidate(); 5487 return true; 5488 } else { 5489 return false; 5490 } 5491 } 5492 5493 /** 5494 * Returns a view containing zoom controls i.e. +/- buttons. The caller is 5495 * in charge of installing this view to the view hierarchy. This view will 5496 * become visible when the user starts scrolling via touch and fade away if 5497 * the user does not interact with it. 5498 * <p/> 5499 * API version 3 introduces a built-in zoom mechanism that is shown 5500 * automatically by the MapView. This is the preferred approach for 5501 * showing the zoom UI. 5502 * 5503 * @deprecated The built-in zoom mechanism is preferred, see 5504 * {@link WebSettings#setBuiltInZoomControls(boolean)}. 5505 */ 5506 @Deprecated 5507 public View getZoomControls() { 5508 if (!getSettings().supportZoom()) { 5509 Log.w(LOGTAG, "This WebView doesn't support zoom."); 5510 return null; 5511 } 5512 return mZoomManager.getExternalZoomPicker(); 5513 } 5514 5515 void dismissZoomControl() { 5516 mZoomManager.dismissZoomPicker(); 5517 } 5518 5519 /** 5520 * Perform zoom in in the webview 5521 * @return TRUE if zoom in succeeds. FALSE if no zoom changes. 5522 */ 5523 public boolean zoomIn() { 5524 // TODO: alternatively we can disallow this during draw history mode 5525 switchOutDrawHistory(); 5526 mZoomManager.mInZoomOverview = false; 5527 // Center zooming to the center of the screen. 5528 mZoomCenterX = getViewWidth() * .5f; 5529 mZoomCenterY = getViewHeight() * .5f; 5530 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 5531 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 5532 return zoomWithPreview(mActualScale * 1.25f, true); 5533 } 5534 5535 /** 5536 * Perform zoom out in the webview 5537 * @return TRUE if zoom out succeeds. FALSE if no zoom changes. 5538 */ 5539 public boolean zoomOut() { 5540 // TODO: alternatively we can disallow this during draw history mode 5541 switchOutDrawHistory(); 5542 // Center zooming to the center of the screen. 5543 mZoomCenterX = getViewWidth() * .5f; 5544 mZoomCenterY = getViewHeight() * .5f; 5545 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 5546 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 5547 return zoomWithPreview(mActualScale * 0.8f, true); 5548 } 5549 5550 private void updateSelection() { 5551 if (mNativeClass == 0) { 5552 return; 5553 } 5554 // mLastTouchX and mLastTouchY are the point in the current viewport 5555 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 5556 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 5557 Rect rect = new Rect(contentX - mNavSlop, contentY - mNavSlop, 5558 contentX + mNavSlop, contentY + mNavSlop); 5559 nativeSelectBestAt(rect); 5560 } 5561 5562 /** 5563 * Scroll the focused text field/area to match the WebTextView 5564 * @param xPercent New x position of the WebTextView from 0 to 1. 5565 * @param y New y position of the WebTextView in view coordinates 5566 */ 5567 /*package*/ void scrollFocusedTextInput(float xPercent, int y) { 5568 if (!inEditingMode() || mWebViewCore == null) { 5569 return; 5570 } 5571 mWebViewCore.sendMessage(EventHub.SCROLL_TEXT_INPUT, 5572 // Since this position is relative to the top of the text input 5573 // field, we do not need to take the title bar's height into 5574 // consideration. 5575 viewToContentDimension(y), 5576 new Float(xPercent)); 5577 } 5578 5579 /** 5580 * Set our starting point and time for a drag from the WebTextView. 5581 */ 5582 /*package*/ void initiateTextFieldDrag(float x, float y, long eventTime) { 5583 if (!inEditingMode()) { 5584 return; 5585 } 5586 mLastTouchX = x + (float) (mWebTextView.getLeft() - mScrollX); 5587 mLastTouchY = y + (float) (mWebTextView.getTop() - mScrollY); 5588 mLastTouchTime = eventTime; 5589 if (!mScroller.isFinished()) { 5590 abortAnimation(); 5591 mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); 5592 } 5593 mSnapScrollMode = SNAP_NONE; 5594 mVelocityTracker = VelocityTracker.obtain(); 5595 mTouchMode = TOUCH_DRAG_START_MODE; 5596 } 5597 5598 /** 5599 * Given a motion event from the WebTextView, set its location to our 5600 * coordinates, and handle the event. 5601 */ 5602 /*package*/ boolean textFieldDrag(MotionEvent event) { 5603 if (!inEditingMode()) { 5604 return false; 5605 } 5606 mDragFromTextInput = true; 5607 event.offsetLocation((float) (mWebTextView.getLeft() - mScrollX), 5608 (float) (mWebTextView.getTop() - mScrollY)); 5609 boolean result = onTouchEvent(event); 5610 mDragFromTextInput = false; 5611 return result; 5612 } 5613 5614 /** 5615 * Due a touch up from a WebTextView. This will be handled by webkit to 5616 * change the selection. 5617 * @param event MotionEvent in the WebTextView's coordinates. 5618 */ 5619 /*package*/ void touchUpOnTextField(MotionEvent event) { 5620 if (!inEditingMode()) { 5621 return; 5622 } 5623 int x = viewToContentX((int) event.getX() + mWebTextView.getLeft()); 5624 int y = viewToContentY((int) event.getY() + mWebTextView.getTop()); 5625 nativeMotionUp(x, y, mNavSlop); 5626 } 5627 5628 /** 5629 * Called when pressing the center key or trackball on a textfield. 5630 */ 5631 /*package*/ void centerKeyPressOnTextField() { 5632 mWebViewCore.sendMessage(EventHub.CLICK, nativeCursorFramePointer(), 5633 nativeCursorNodePointer()); 5634 } 5635 5636 private void doShortPress() { 5637 if (mNativeClass == 0) { 5638 return; 5639 } 5640 if (mPreventDefault == PREVENT_DEFAULT_YES) { 5641 return; 5642 } 5643 mTouchMode = TOUCH_DONE_MODE; 5644 switchOutDrawHistory(); 5645 // mLastTouchX and mLastTouchY are the point in the current viewport 5646 int contentX = viewToContentX((int) mLastTouchX + mScrollX); 5647 int contentY = viewToContentY((int) mLastTouchY + mScrollY); 5648 if (nativePointInNavCache(contentX, contentY, mNavSlop)) { 5649 WebViewCore.MotionUpData motionUpData = new WebViewCore 5650 .MotionUpData(); 5651 motionUpData.mFrame = nativeCacheHitFramePointer(); 5652 motionUpData.mNode = nativeCacheHitNodePointer(); 5653 motionUpData.mBounds = nativeCacheHitNodeBounds(); 5654 motionUpData.mX = contentX; 5655 motionUpData.mY = contentY; 5656 mWebViewCore.sendMessageAtFrontOfQueue(EventHub.VALID_NODE_BOUNDS, 5657 motionUpData); 5658 } else { 5659 doMotionUp(contentX, contentY); 5660 } 5661 } 5662 5663 private void doMotionUp(int contentX, int contentY) { 5664 if (mLogEvent && nativeMotionUp(contentX, contentY, mNavSlop)) { 5665 EventLog.writeEvent(EventLogTags.BROWSER_SNAP_CENTER); 5666 } 5667 if (nativeHasCursorNode() && !nativeCursorIsTextInput()) { 5668 playSoundEffect(SoundEffectConstants.CLICK); 5669 } 5670 } 5671 5672 /* 5673 * Return true if the view (Plugin) is fully visible and maximized inside 5674 * the WebView. 5675 */ 5676 private boolean isPluginFitOnScreen(ViewManager.ChildView view) { 5677 int viewWidth = getViewWidth(); 5678 int viewHeight = getViewHeightWithTitle(); 5679 float scale = Math.min((float) viewWidth / view.width, 5680 (float) viewHeight / view.height); 5681 if (scale < mZoomManager.mMinZoomScale) { 5682 scale = mZoomManager.mMinZoomScale; 5683 } else if (scale > mZoomManager.mMaxZoomScale) { 5684 scale = mZoomManager.mMaxZoomScale; 5685 } 5686 if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) { 5687 if (contentToViewX(view.x) >= mScrollX 5688 && contentToViewX(view.x + view.width) <= mScrollX 5689 + viewWidth 5690 && contentToViewY(view.y) >= mScrollY 5691 && contentToViewY(view.y + view.height) <= mScrollY 5692 + viewHeight) { 5693 return true; 5694 } 5695 } 5696 return false; 5697 } 5698 5699 /* 5700 * Maximize and center the rectangle, specified in the document coordinate 5701 * space, inside the WebView. If the zoom doesn't need to be changed, do an 5702 * animated scroll to center it. If the zoom needs to be changed, find the 5703 * zoom center and do a smooth zoom transition. 5704 */ 5705 private void centerFitRect(int docX, int docY, int docWidth, int docHeight) { 5706 int viewWidth = getViewWidth(); 5707 int viewHeight = getViewHeightWithTitle(); 5708 float scale = Math.min((float) viewWidth / docWidth, (float) viewHeight 5709 / docHeight); 5710 if (scale < mZoomManager.mMinZoomScale) { 5711 scale = mZoomManager.mMinZoomScale; 5712 } else if (scale > mZoomManager.mMaxZoomScale) { 5713 scale = mZoomManager.mMaxZoomScale; 5714 } 5715 if (Math.abs(scale - mActualScale) < MINIMUM_SCALE_INCREMENT) { 5716 pinScrollTo(contentToViewX(docX + docWidth / 2) - viewWidth / 2, 5717 contentToViewY(docY + docHeight / 2) - viewHeight / 2, 5718 true, 0); 5719 } else { 5720 float oldScreenX = docX * mActualScale - mScrollX; 5721 float rectViewX = docX * scale; 5722 float rectViewWidth = docWidth * scale; 5723 float newMaxWidth = mContentWidth * scale; 5724 float newScreenX = (viewWidth - rectViewWidth) / 2; 5725 // pin the newX to the WebView 5726 if (newScreenX > rectViewX) { 5727 newScreenX = rectViewX; 5728 } else if (newScreenX > (newMaxWidth - rectViewX - rectViewWidth)) { 5729 newScreenX = viewWidth - (newMaxWidth - rectViewX); 5730 } 5731 mZoomCenterX = (oldScreenX * scale - newScreenX * mActualScale) 5732 / (scale - mActualScale); 5733 float oldScreenY = docY * mActualScale + getTitleHeight() 5734 - mScrollY; 5735 float rectViewY = docY * scale + getTitleHeight(); 5736 float rectViewHeight = docHeight * scale; 5737 float newMaxHeight = mContentHeight * scale + getTitleHeight(); 5738 float newScreenY = (viewHeight - rectViewHeight) / 2; 5739 // pin the newY to the WebView 5740 if (newScreenY > rectViewY) { 5741 newScreenY = rectViewY; 5742 } else if (newScreenY > (newMaxHeight - rectViewY - rectViewHeight)) { 5743 newScreenY = viewHeight - (newMaxHeight - rectViewY); 5744 } 5745 mZoomCenterY = (oldScreenY * scale - newScreenY * mActualScale) 5746 / (scale - mActualScale); 5747 zoomWithPreview(scale, false); 5748 } 5749 } 5750 5751 // Rule for double tap: 5752 // 1. if the current scale is not same as the text wrap scale and layout 5753 // algorithm is NARROW_COLUMNS, fit to column; 5754 // 2. if the current state is not overview mode, change to overview mode; 5755 // 3. if the current state is overview mode, change to default scale. 5756 private void doDoubleTap() { 5757 if (mWebViewCore.getSettings().getUseWideViewPort() == false) { 5758 return; 5759 } 5760 mZoomCenterX = mLastTouchX; 5761 mZoomCenterY = mLastTouchY; 5762 mAnchorX = viewToContentX((int) mZoomCenterX + mScrollX); 5763 mAnchorY = viewToContentY((int) mZoomCenterY + mScrollY); 5764 WebSettings settings = getSettings(); 5765 settings.setDoubleTapToastCount(0); 5766 // remove the zoom control after double tap 5767 mZoomManager.dismissZoomPicker(); 5768 ViewManager.ChildView plugin = mViewManager.hitTest(mAnchorX, mAnchorY); 5769 if (plugin != null) { 5770 if (isPluginFitOnScreen(plugin)) { 5771 mZoomManager.mInZoomOverview = true; 5772 // Force the titlebar fully reveal in overview mode 5773 if (mScrollY < getTitleHeight()) mScrollY = 0; 5774 zoomWithPreview((float) getViewWidth() / mZoomOverviewWidth, 5775 true); 5776 } else { 5777 mZoomManager.mInZoomOverview = false; 5778 centerFitRect(plugin.x, plugin.y, plugin.width, plugin.height); 5779 } 5780 return; 5781 } 5782 boolean zoomToDefault = false; 5783 if ((settings.getLayoutAlgorithm() == WebSettings.LayoutAlgorithm.NARROW_COLUMNS) 5784 && (Math.abs(mActualScale - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT)) { 5785 setNewZoomScale(mActualScale, true, true); 5786 float overviewScale = (float) getViewWidth() / mZoomOverviewWidth; 5787 if (Math.abs(mActualScale - overviewScale) < MINIMUM_SCALE_INCREMENT) { 5788 mZoomManager.mInZoomOverview = true; 5789 } 5790 } else if (!mZoomManager.mInZoomOverview) { 5791 float newScale = (float) getViewWidth() / mZoomOverviewWidth; 5792 if (Math.abs(mActualScale - newScale) >= MINIMUM_SCALE_INCREMENT) { 5793 mZoomManager.mInZoomOverview = true; 5794 // Force the titlebar fully reveal in overview mode 5795 if (mScrollY < getTitleHeight()) mScrollY = 0; 5796 zoomWithPreview(newScale, true); 5797 } else if (Math.abs(mActualScale - mDefaultScale) >= MINIMUM_SCALE_INCREMENT) { 5798 zoomToDefault = true; 5799 } 5800 } else { 5801 zoomToDefault = true; 5802 } 5803 if (zoomToDefault) { 5804 mZoomManager.mInZoomOverview = false; 5805 int left = nativeGetBlockLeftEdge(mAnchorX, mAnchorY, mActualScale); 5806 if (left != NO_LEFTEDGE) { 5807 // add a 5pt padding to the left edge. 5808 int viewLeft = contentToViewX(left < 5 ? 0 : (left - 5)) 5809 - mScrollX; 5810 // Re-calculate the zoom center so that the new scroll x will be 5811 // on the left edge. 5812 if (viewLeft > 0) { 5813 mZoomCenterX = viewLeft * mDefaultScale 5814 / (mDefaultScale - mActualScale); 5815 } else { 5816 scrollBy(viewLeft, 0); 5817 mZoomCenterX = 0; 5818 } 5819 } 5820 zoomWithPreview(mDefaultScale, true); 5821 } 5822 } 5823 5824 // Called by JNI to handle a touch on a node representing an email address, 5825 // address, or phone number 5826 private void overrideLoading(String url) { 5827 mCallbackProxy.uiOverrideUrlLoading(url); 5828 } 5829 5830 @Override 5831 public boolean requestFocus(int direction, Rect previouslyFocusedRect) { 5832 // FIXME: If a subwindow is showing find, and the user touches the 5833 // background window, it can steal focus. 5834 if (mFindIsUp) return false; 5835 boolean result = false; 5836 if (inEditingMode()) { 5837 result = mWebTextView.requestFocus(direction, 5838 previouslyFocusedRect); 5839 } else { 5840 result = super.requestFocus(direction, previouslyFocusedRect); 5841 if (mWebViewCore.getSettings().getNeedInitialFocus()) { 5842 // For cases such as GMail, where we gain focus from a direction, 5843 // we want to move to the first available link. 5844 // FIXME: If there are no visible links, we may not want to 5845 int fakeKeyDirection = 0; 5846 switch(direction) { 5847 case View.FOCUS_UP: 5848 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_UP; 5849 break; 5850 case View.FOCUS_DOWN: 5851 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_DOWN; 5852 break; 5853 case View.FOCUS_LEFT: 5854 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_LEFT; 5855 break; 5856 case View.FOCUS_RIGHT: 5857 fakeKeyDirection = KeyEvent.KEYCODE_DPAD_RIGHT; 5858 break; 5859 default: 5860 return result; 5861 } 5862 if (mNativeClass != 0 && !nativeHasCursorNode()) { 5863 navHandledKey(fakeKeyDirection, 1, true, 0); 5864 } 5865 } 5866 } 5867 return result; 5868 } 5869 5870 @Override 5871 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 5872 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 5873 5874 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 5875 int heightSize = MeasureSpec.getSize(heightMeasureSpec); 5876 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 5877 int widthSize = MeasureSpec.getSize(widthMeasureSpec); 5878 5879 int measuredHeight = heightSize; 5880 int measuredWidth = widthSize; 5881 5882 // Grab the content size from WebViewCore. 5883 int contentHeight = contentToViewDimension(mContentHeight); 5884 int contentWidth = contentToViewDimension(mContentWidth); 5885 5886// Log.d(LOGTAG, "------- measure " + heightMode); 5887 5888 if (heightMode != MeasureSpec.EXACTLY) { 5889 mHeightCanMeasure = true; 5890 measuredHeight = contentHeight; 5891 if (heightMode == MeasureSpec.AT_MOST) { 5892 // If we are larger than the AT_MOST height, then our height can 5893 // no longer be measured and we should scroll internally. 5894 if (measuredHeight > heightSize) { 5895 measuredHeight = heightSize; 5896 mHeightCanMeasure = false; 5897 } 5898 } 5899 } else { 5900 mHeightCanMeasure = false; 5901 } 5902 if (mNativeClass != 0) { 5903 nativeSetHeightCanMeasure(mHeightCanMeasure); 5904 } 5905 // For the width, always use the given size unless unspecified. 5906 if (widthMode == MeasureSpec.UNSPECIFIED) { 5907 mWidthCanMeasure = true; 5908 measuredWidth = contentWidth; 5909 } else { 5910 mWidthCanMeasure = false; 5911 } 5912 5913 synchronized (this) { 5914 setMeasuredDimension(measuredWidth, measuredHeight); 5915 } 5916 } 5917 5918 @Override 5919 public boolean requestChildRectangleOnScreen(View child, 5920 Rect rect, 5921 boolean immediate) { 5922 rect.offset(child.getLeft() - child.getScrollX(), 5923 child.getTop() - child.getScrollY()); 5924 5925 Rect content = new Rect(viewToContentX(mScrollX), 5926 viewToContentY(mScrollY), 5927 viewToContentX(mScrollX + getWidth() 5928 - getVerticalScrollbarWidth()), 5929 viewToContentY(mScrollY + getViewHeightWithTitle())); 5930 content = nativeSubtractLayers(content); 5931 int screenTop = contentToViewY(content.top); 5932 int screenBottom = contentToViewY(content.bottom); 5933 int height = screenBottom - screenTop; 5934 int scrollYDelta = 0; 5935 5936 if (rect.bottom > screenBottom) { 5937 int oneThirdOfScreenHeight = height / 3; 5938 if (rect.height() > 2 * oneThirdOfScreenHeight) { 5939 // If the rectangle is too tall to fit in the bottom two thirds 5940 // of the screen, place it at the top. 5941 scrollYDelta = rect.top - screenTop; 5942 } else { 5943 // If the rectangle will still fit on screen, we want its 5944 // top to be in the top third of the screen. 5945 scrollYDelta = rect.top - (screenTop + oneThirdOfScreenHeight); 5946 } 5947 } else if (rect.top < screenTop) { 5948 scrollYDelta = rect.top - screenTop; 5949 } 5950 5951 int screenLeft = contentToViewX(content.left); 5952 int screenRight = contentToViewX(content.right); 5953 int width = screenRight - screenLeft; 5954 int scrollXDelta = 0; 5955 5956 if (rect.right > screenRight && rect.left > screenLeft) { 5957 if (rect.width() > width) { 5958 scrollXDelta += (rect.left - screenLeft); 5959 } else { 5960 scrollXDelta += (rect.right - screenRight); 5961 } 5962 } else if (rect.left < screenLeft) { 5963 scrollXDelta -= (screenLeft - rect.left); 5964 } 5965 5966 if ((scrollYDelta | scrollXDelta) != 0) { 5967 return pinScrollBy(scrollXDelta, scrollYDelta, !immediate, 0); 5968 } 5969 5970 return false; 5971 } 5972 5973 /* package */ void replaceTextfieldText(int oldStart, int oldEnd, 5974 String replace, int newStart, int newEnd) { 5975 WebViewCore.ReplaceTextData arg = new WebViewCore.ReplaceTextData(); 5976 arg.mReplace = replace; 5977 arg.mNewStart = newStart; 5978 arg.mNewEnd = newEnd; 5979 mTextGeneration++; 5980 arg.mTextGeneration = mTextGeneration; 5981 mWebViewCore.sendMessage(EventHub.REPLACE_TEXT, oldStart, oldEnd, arg); 5982 } 5983 5984 /* package */ void passToJavaScript(String currentText, KeyEvent event) { 5985 WebViewCore.JSKeyData arg = new WebViewCore.JSKeyData(); 5986 arg.mEvent = event; 5987 arg.mCurrentText = currentText; 5988 // Increase our text generation number, and pass it to webcore thread 5989 mTextGeneration++; 5990 mWebViewCore.sendMessage(EventHub.PASS_TO_JS, mTextGeneration, 0, arg); 5991 // WebKit's document state is not saved until about to leave the page. 5992 // To make sure the host application, like Browser, has the up to date 5993 // document state when it goes to background, we force to save the 5994 // document state. 5995 mWebViewCore.removeMessages(EventHub.SAVE_DOCUMENT_STATE); 5996 mWebViewCore.sendMessageDelayed(EventHub.SAVE_DOCUMENT_STATE, 5997 cursorData(), 1000); 5998 } 5999 6000 /* package */ synchronized WebViewCore getWebViewCore() { 6001 return mWebViewCore; 6002 } 6003 6004 //------------------------------------------------------------------------- 6005 // Methods can be called from a separate thread, like WebViewCore 6006 // If it needs to call the View system, it has to send message. 6007 //------------------------------------------------------------------------- 6008 6009 /** 6010 * General handler to receive message coming from webkit thread 6011 */ 6012 class PrivateHandler extends Handler { 6013 @Override 6014 public void handleMessage(Message msg) { 6015 // exclude INVAL_RECT_MSG_ID since it is frequently output 6016 if (DebugFlags.WEB_VIEW && msg.what != INVAL_RECT_MSG_ID) { 6017 if (msg.what >= FIRST_PRIVATE_MSG_ID 6018 && msg.what <= LAST_PRIVATE_MSG_ID) { 6019 Log.v(LOGTAG, HandlerPrivateDebugString[msg.what 6020 - FIRST_PRIVATE_MSG_ID]); 6021 } else if (msg.what >= FIRST_PACKAGE_MSG_ID 6022 && msg.what <= LAST_PACKAGE_MSG_ID) { 6023 Log.v(LOGTAG, HandlerPackageDebugString[msg.what 6024 - FIRST_PACKAGE_MSG_ID]); 6025 } else { 6026 Log.v(LOGTAG, Integer.toString(msg.what)); 6027 } 6028 } 6029 if (mWebViewCore == null) { 6030 // after WebView's destroy() is called, skip handling messages. 6031 return; 6032 } 6033 switch (msg.what) { 6034 case REMEMBER_PASSWORD: { 6035 mDatabase.setUsernamePassword( 6036 msg.getData().getString("host"), 6037 msg.getData().getString("username"), 6038 msg.getData().getString("password")); 6039 ((Message) msg.obj).sendToTarget(); 6040 break; 6041 } 6042 case NEVER_REMEMBER_PASSWORD: { 6043 mDatabase.setUsernamePassword( 6044 msg.getData().getString("host"), null, null); 6045 ((Message) msg.obj).sendToTarget(); 6046 break; 6047 } 6048 case PREVENT_DEFAULT_TIMEOUT: { 6049 // if timeout happens, cancel it so that it won't block UI 6050 // to continue handling touch events 6051 if ((msg.arg1 == MotionEvent.ACTION_DOWN 6052 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) 6053 || (msg.arg1 == MotionEvent.ACTION_MOVE 6054 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN)) { 6055 cancelWebCoreTouchEvent( 6056 viewToContentX((int) mLastTouchX + mScrollX), 6057 viewToContentY((int) mLastTouchY + mScrollY), 6058 true); 6059 } 6060 break; 6061 } 6062 case SWITCH_TO_SHORTPRESS: { 6063 if (mTouchMode == TOUCH_INIT_MODE) { 6064 if (mPreventDefault != PREVENT_DEFAULT_YES) { 6065 mTouchMode = TOUCH_SHORTPRESS_START_MODE; 6066 updateSelection(); 6067 } else { 6068 // set to TOUCH_SHORTPRESS_MODE so that it won't 6069 // trigger double tap any more 6070 mTouchMode = TOUCH_SHORTPRESS_MODE; 6071 } 6072 } else if (mTouchMode == TOUCH_DOUBLE_TAP_MODE) { 6073 mTouchMode = TOUCH_DONE_MODE; 6074 } 6075 break; 6076 } 6077 case SWITCH_TO_LONGPRESS: { 6078 if (inFullScreenMode() || mDeferTouchProcess) { 6079 TouchEventData ted = new TouchEventData(); 6080 ted.mAction = WebViewCore.ACTION_LONGPRESS; 6081 ted.mX = viewToContentX((int) mLastTouchX + mScrollX); 6082 ted.mY = viewToContentY((int) mLastTouchY + mScrollY); 6083 // metaState for long press is tricky. Should it be the 6084 // state when the press started or when the press was 6085 // released? Or some intermediary key state? For 6086 // simplicity for now, we don't set it. 6087 ted.mMetaState = 0; 6088 ted.mReprocess = mDeferTouchProcess; 6089 mWebViewCore.sendMessage(EventHub.TOUCH_EVENT, ted); 6090 } else if (mPreventDefault != PREVENT_DEFAULT_YES) { 6091 mTouchMode = TOUCH_DONE_MODE; 6092 performLongClick(); 6093 } 6094 break; 6095 } 6096 case RELEASE_SINGLE_TAP: { 6097 doShortPress(); 6098 break; 6099 } 6100 case SCROLL_BY_MSG_ID: 6101 setContentScrollBy(msg.arg1, msg.arg2, (Boolean) msg.obj); 6102 break; 6103 case SYNC_SCROLL_TO_MSG_ID: 6104 if (mUserScroll) { 6105 // if user has scrolled explicitly, don't sync the 6106 // scroll position any more 6107 mUserScroll = false; 6108 break; 6109 } 6110 // fall through 6111 case SCROLL_TO_MSG_ID: 6112 if (setContentScrollTo(msg.arg1, msg.arg2)) { 6113 // if we can't scroll to the exact position due to pin, 6114 // send a message to WebCore to re-scroll when we get a 6115 // new picture 6116 mUserScroll = false; 6117 mWebViewCore.sendMessage(EventHub.SYNC_SCROLL, 6118 msg.arg1, msg.arg2); 6119 } 6120 break; 6121 case SPAWN_SCROLL_TO_MSG_ID: 6122 spawnContentScrollTo(msg.arg1, msg.arg2); 6123 break; 6124 case UPDATE_ZOOM_RANGE: { 6125 WebViewCore.RestoreState restoreState 6126 = (WebViewCore.RestoreState) msg.obj; 6127 // mScrollX contains the new minPrefWidth 6128 updateZoomRange(restoreState, getViewWidth(), 6129 restoreState.mScrollX, false); 6130 break; 6131 } 6132 case NEW_PICTURE_MSG_ID: { 6133 // If we've previously delayed deleting a root 6134 // layer, do it now. 6135 if (mDelayedDeleteRootLayer) { 6136 mDelayedDeleteRootLayer = false; 6137 nativeSetRootLayer(0); 6138 } 6139 WebSettings settings = mWebViewCore.getSettings(); 6140 // called for new content 6141 final int viewWidth = getViewWidth(); 6142 final WebViewCore.DrawData draw = 6143 (WebViewCore.DrawData) msg.obj; 6144 final Point viewSize = draw.mViewPoint; 6145 boolean useWideViewport = settings.getUseWideViewPort(); 6146 WebViewCore.RestoreState restoreState = draw.mRestoreState; 6147 boolean hasRestoreState = restoreState != null; 6148 if (hasRestoreState) { 6149 updateZoomRange(restoreState, viewSize.x, 6150 draw.mMinPrefWidth, true); 6151 if (!mDrawHistory) { 6152 mZoomManager.mInZoomOverview = false; 6153 6154 if (mInitialScaleInPercent > 0) { 6155 setNewZoomScale(mInitialScaleInPercent / 100.0f, 6156 mInitialScaleInPercent != mTextWrapScale * 100, 6157 false); 6158 } else if (restoreState.mViewScale > 0) { 6159 mTextWrapScale = restoreState.mTextWrapScale; 6160 setNewZoomScale(restoreState.mViewScale, false, 6161 false); 6162 } else { 6163 mZoomManager.mInZoomOverview = useWideViewport 6164 && settings.getLoadWithOverviewMode(); 6165 float scale; 6166 if (mZoomManager.mInZoomOverview) { 6167 scale = (float) viewWidth 6168 / DEFAULT_VIEWPORT_WIDTH; 6169 } else { 6170 scale = restoreState.mTextWrapScale; 6171 } 6172 setNewZoomScale(scale, Math.abs(scale 6173 - mTextWrapScale) >= MINIMUM_SCALE_INCREMENT, 6174 false); 6175 } 6176 setContentScrollTo(restoreState.mScrollX, 6177 restoreState.mScrollY); 6178 // As we are on a new page, remove the WebTextView. This 6179 // is necessary for page loads driven by webkit, and in 6180 // particular when the user was on a password field, so 6181 // the WebTextView was visible. 6182 clearTextEntry(false); 6183 // update the zoom buttons as the scale can be changed 6184 mZoomManager.updateZoomPicker(); 6185 } 6186 } 6187 // We update the layout (i.e. request a layout from the 6188 // view system) if the last view size that we sent to 6189 // WebCore matches the view size of the picture we just 6190 // received in the fixed dimension. 6191 final boolean updateLayout = viewSize.x == mLastWidthSent 6192 && viewSize.y == mLastHeightSent; 6193 recordNewContentSize(draw.mWidthHeight.x, 6194 draw.mWidthHeight.y, updateLayout); 6195 if (DebugFlags.WEB_VIEW) { 6196 Rect b = draw.mInvalRegion.getBounds(); 6197 Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + 6198 b.left+","+b.top+","+b.right+","+b.bottom+"}"); 6199 } 6200 invalidateContentRect(draw.mInvalRegion.getBounds()); 6201 if (mPictureListener != null) { 6202 mPictureListener.onNewPicture(WebView.this, capturePicture()); 6203 } 6204 if (useWideViewport) { 6205 // limit mZoomOverviewWidth upper bound to 6206 // sMaxViewportWidth so that if the page doesn't behave 6207 // well, the WebView won't go insane. limit the lower 6208 // bound to match the default scale for mobile sites. 6209 mZoomOverviewWidth = Math.min(sMaxViewportWidth, Math 6210 .max((int) (viewWidth / mDefaultScale), Math 6211 .max(draw.mMinPrefWidth, 6212 draw.mViewPoint.x))); 6213 } 6214 if (!mZoomManager.mMinZoomScaleFixed) { 6215 mZoomManager.mMinZoomScale = (float) viewWidth / mZoomOverviewWidth; 6216 } 6217 if (!mDrawHistory && mZoomManager.mInZoomOverview) { 6218 // fit the content width to the current view. Ignore 6219 // the rounding error case. 6220 if (Math.abs((viewWidth * mInvActualScale) 6221 - mZoomOverviewWidth) > 1) { 6222 setNewZoomScale((float) viewWidth 6223 / mZoomOverviewWidth, Math.abs(mActualScale 6224 - mTextWrapScale) < MINIMUM_SCALE_INCREMENT, 6225 false); 6226 } 6227 } 6228 if (draw.mFocusSizeChanged && inEditingMode()) { 6229 mFocusSizeChanged = true; 6230 } 6231 if (hasRestoreState) { 6232 mViewManager.postReadyToDrawAll(); 6233 } 6234 break; 6235 } 6236 case WEBCORE_INITIALIZED_MSG_ID: 6237 // nativeCreate sets mNativeClass to a non-zero value 6238 nativeCreate(msg.arg1); 6239 break; 6240 case UPDATE_TEXTFIELD_TEXT_MSG_ID: 6241 // Make sure that the textfield is currently focused 6242 // and representing the same node as the pointer. 6243 if (inEditingMode() && 6244 mWebTextView.isSameTextField(msg.arg1)) { 6245 if (msg.getData().getBoolean("password")) { 6246 Spannable text = (Spannable) mWebTextView.getText(); 6247 int start = Selection.getSelectionStart(text); 6248 int end = Selection.getSelectionEnd(text); 6249 mWebTextView.setInPassword(true); 6250 // Restore the selection, which may have been 6251 // ruined by setInPassword. 6252 Spannable pword = 6253 (Spannable) mWebTextView.getText(); 6254 Selection.setSelection(pword, start, end); 6255 // If the text entry has created more events, ignore 6256 // this one. 6257 } else if (msg.arg2 == mTextGeneration) { 6258 mWebTextView.setTextAndKeepSelection( 6259 (String) msg.obj); 6260 } 6261 } 6262 break; 6263 case REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID: 6264 displaySoftKeyboard(true); 6265 // fall through to UPDATE_TEXT_SELECTION_MSG_ID 6266 case UPDATE_TEXT_SELECTION_MSG_ID: 6267 updateTextSelectionFromMessage(msg.arg1, msg.arg2, 6268 (WebViewCore.TextSelectionData) msg.obj); 6269 break; 6270 case RETURN_LABEL: 6271 if (inEditingMode() 6272 && mWebTextView.isSameTextField(msg.arg1)) { 6273 mWebTextView.setHint((String) msg.obj); 6274 InputMethodManager imm 6275 = InputMethodManager.peekInstance(); 6276 // The hint is propagated to the IME in 6277 // onCreateInputConnection. If the IME is already 6278 // active, restart it so that its hint text is updated. 6279 if (imm != null && imm.isActive(mWebTextView)) { 6280 imm.restartInput(mWebTextView); 6281 } 6282 } 6283 break; 6284 case UNHANDLED_NAV_KEY: 6285 navHandledKey(msg.arg1, 1, false, 0); 6286 break; 6287 case UPDATE_TEXT_ENTRY_MSG_ID: 6288 // this is sent after finishing resize in WebViewCore. Make 6289 // sure the text edit box is still on the screen. 6290 if (inEditingMode() && nativeCursorIsTextInput()) { 6291 mWebTextView.bringIntoView(); 6292 rebuildWebTextView(); 6293 } 6294 break; 6295 case CLEAR_TEXT_ENTRY: 6296 clearTextEntry(false); 6297 break; 6298 case INVAL_RECT_MSG_ID: { 6299 Rect r = (Rect)msg.obj; 6300 if (r == null) { 6301 invalidate(); 6302 } else { 6303 // we need to scale r from content into view coords, 6304 // which viewInvalidate() does for us 6305 viewInvalidate(r.left, r.top, r.right, r.bottom); 6306 } 6307 break; 6308 } 6309 case IMMEDIATE_REPAINT_MSG_ID: { 6310 invalidate(); 6311 break; 6312 } 6313 case SET_ROOT_LAYER_MSG_ID: { 6314 if (0 == msg.arg1) { 6315 // Null indicates deleting the old layer, but 6316 // don't actually do so until we've got the 6317 // new page to display. 6318 mDelayedDeleteRootLayer = true; 6319 } else { 6320 mDelayedDeleteRootLayer = false; 6321 nativeSetRootLayer(msg.arg1); 6322 invalidate(); 6323 } 6324 break; 6325 } 6326 case REQUEST_FORM_DATA: 6327 AutoCompleteAdapter adapter = (AutoCompleteAdapter) msg.obj; 6328 if (mWebTextView.isSameTextField(msg.arg1)) { 6329 mWebTextView.setAdapterCustom(adapter); 6330 } 6331 break; 6332 case RESUME_WEBCORE_PRIORITY: 6333 WebViewCore.resumePriority(); 6334 WebViewCore.resumeUpdatePicture(mWebViewCore); 6335 break; 6336 6337 case LONG_PRESS_CENTER: 6338 // as this is shared by keydown and trackballdown, reset all 6339 // the states 6340 mGotCenterDown = false; 6341 mTrackballDown = false; 6342 performLongClick(); 6343 break; 6344 6345 case WEBCORE_NEED_TOUCH_EVENTS: 6346 mForwardTouchEvents = (msg.arg1 != 0); 6347 break; 6348 6349 case PREVENT_TOUCH_ID: 6350 if (inFullScreenMode()) { 6351 break; 6352 } 6353 if (msg.obj == null) { 6354 if (msg.arg1 == MotionEvent.ACTION_DOWN 6355 && mPreventDefault == PREVENT_DEFAULT_MAYBE_YES) { 6356 // if prevent default is called from WebCore, UI 6357 // will not handle the rest of the touch events any 6358 // more. 6359 mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES 6360 : PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN; 6361 } else if (msg.arg1 == MotionEvent.ACTION_MOVE 6362 && mPreventDefault == PREVENT_DEFAULT_NO_FROM_TOUCH_DOWN) { 6363 // the return for the first ACTION_MOVE will decide 6364 // whether UI will handle touch or not. Currently no 6365 // support for alternating prevent default 6366 mPreventDefault = msg.arg2 == 1 ? PREVENT_DEFAULT_YES 6367 : PREVENT_DEFAULT_NO; 6368 } 6369 } else if (msg.arg2 == 0) { 6370 // prevent default is not called in WebCore, so the 6371 // message needs to be reprocessed in UI 6372 TouchEventData ted = (TouchEventData) msg.obj; 6373 switch (ted.mAction) { 6374 case MotionEvent.ACTION_DOWN: 6375 mLastDeferTouchX = contentToViewX(ted.mX) 6376 - mScrollX; 6377 mLastDeferTouchY = contentToViewY(ted.mY) 6378 - mScrollY; 6379 mDeferTouchMode = TOUCH_INIT_MODE; 6380 break; 6381 case MotionEvent.ACTION_MOVE: { 6382 // no snapping in defer process 6383 int x = contentToViewX(ted.mX) - mScrollX; 6384 int y = contentToViewY(ted.mY) - mScrollY; 6385 if (mDeferTouchMode != TOUCH_DRAG_MODE) { 6386 mDeferTouchMode = TOUCH_DRAG_MODE; 6387 mLastDeferTouchX = x; 6388 mLastDeferTouchY = y; 6389 startDrag(); 6390 } 6391 int deltaX = pinLocX((int) (mScrollX 6392 + mLastDeferTouchX - x)) 6393 - mScrollX; 6394 int deltaY = pinLocY((int) (mScrollY 6395 + mLastDeferTouchY - y)) 6396 - mScrollY; 6397 doDrag(deltaX, deltaY); 6398 if (deltaX != 0) mLastDeferTouchX = x; 6399 if (deltaY != 0) mLastDeferTouchY = y; 6400 break; 6401 } 6402 case MotionEvent.ACTION_UP: 6403 case MotionEvent.ACTION_CANCEL: 6404 if (mDeferTouchMode == TOUCH_DRAG_MODE) { 6405 // no fling in defer process 6406 WebViewCore.resumePriority(); 6407 WebViewCore.resumeUpdatePicture(mWebViewCore); 6408 } 6409 mDeferTouchMode = TOUCH_DONE_MODE; 6410 break; 6411 case WebViewCore.ACTION_DOUBLETAP: 6412 // doDoubleTap() needs mLastTouchX/Y as anchor 6413 mLastTouchX = contentToViewX(ted.mX) - mScrollX; 6414 mLastTouchY = contentToViewY(ted.mY) - mScrollY; 6415 doDoubleTap(); 6416 mDeferTouchMode = TOUCH_DONE_MODE; 6417 break; 6418 case WebViewCore.ACTION_LONGPRESS: 6419 HitTestResult hitTest = getHitTestResult(); 6420 if (hitTest != null && hitTest.mType 6421 != HitTestResult.UNKNOWN_TYPE) { 6422 performLongClick(); 6423 } 6424 mDeferTouchMode = TOUCH_DONE_MODE; 6425 break; 6426 } 6427 } 6428 break; 6429 6430 case REQUEST_KEYBOARD: 6431 if (msg.arg1 == 0) { 6432 hideSoftKeyboard(); 6433 } else { 6434 displaySoftKeyboard(false); 6435 } 6436 break; 6437 6438 case FIND_AGAIN: 6439 // Ignore if find has been dismissed. 6440 if (mFindIsUp) { 6441 findAll(mLastFind); 6442 } 6443 break; 6444 6445 case DRAG_HELD_MOTIONLESS: 6446 mHeldMotionless = MOTIONLESS_TRUE; 6447 invalidate(); 6448 // fall through to keep scrollbars awake 6449 6450 case AWAKEN_SCROLL_BARS: 6451 if (mTouchMode == TOUCH_DRAG_MODE 6452 && mHeldMotionless == MOTIONLESS_TRUE) { 6453 awakenScrollBars(ViewConfiguration 6454 .getScrollDefaultDelay(), false); 6455 mPrivateHandler.sendMessageDelayed(mPrivateHandler 6456 .obtainMessage(AWAKEN_SCROLL_BARS), 6457 ViewConfiguration.getScrollDefaultDelay()); 6458 } 6459 break; 6460 6461 case DO_MOTION_UP: 6462 doMotionUp(msg.arg1, msg.arg2); 6463 break; 6464 6465 case SHOW_FULLSCREEN: { 6466 View view = (View) msg.obj; 6467 int npp = msg.arg1; 6468 6469 if (mFullScreenHolder != null) { 6470 Log.w(LOGTAG, "Should not have another full screen."); 6471 mFullScreenHolder.dismiss(); 6472 } 6473 mFullScreenHolder = new PluginFullScreenHolder(WebView.this, npp); 6474 mFullScreenHolder.setContentView(view); 6475 mFullScreenHolder.setCancelable(false); 6476 mFullScreenHolder.setCanceledOnTouchOutside(false); 6477 mFullScreenHolder.show(); 6478 6479 break; 6480 } 6481 case HIDE_FULLSCREEN: 6482 if (inFullScreenMode()) { 6483 mFullScreenHolder.dismiss(); 6484 mFullScreenHolder = null; 6485 } 6486 break; 6487 6488 case DOM_FOCUS_CHANGED: 6489 if (inEditingMode()) { 6490 nativeClearCursor(); 6491 rebuildWebTextView(); 6492 } 6493 break; 6494 6495 case SHOW_RECT_MSG_ID: { 6496 WebViewCore.ShowRectData data = (WebViewCore.ShowRectData) msg.obj; 6497 int x = mScrollX; 6498 int left = contentToViewX(data.mLeft); 6499 int width = contentToViewDimension(data.mWidth); 6500 int maxWidth = contentToViewDimension(data.mContentWidth); 6501 int viewWidth = getViewWidth(); 6502 if (width < viewWidth) { 6503 // center align 6504 x += left + width / 2 - mScrollX - viewWidth / 2; 6505 } else { 6506 x += (int) (left + data.mXPercentInDoc * width 6507 - mScrollX - data.mXPercentInView * viewWidth); 6508 } 6509 if (DebugFlags.WEB_VIEW) { 6510 Log.v(LOGTAG, "showRectMsg=(left=" + left + ",width=" + 6511 width + ",maxWidth=" + maxWidth + 6512 ",viewWidth=" + viewWidth + ",x=" 6513 + x + ",xPercentInDoc=" + data.mXPercentInDoc + 6514 ",xPercentInView=" + data.mXPercentInView+ ")"); 6515 } 6516 // use the passing content width to cap x as the current 6517 // mContentWidth may not be updated yet 6518 x = Math.max(0, 6519 (Math.min(maxWidth, x + viewWidth)) - viewWidth); 6520 int top = contentToViewY(data.mTop); 6521 int height = contentToViewDimension(data.mHeight); 6522 int maxHeight = contentToViewDimension(data.mContentHeight); 6523 int viewHeight = getViewHeight(); 6524 int y = (int) (top + data.mYPercentInDoc * height - 6525 data.mYPercentInView * viewHeight); 6526 if (DebugFlags.WEB_VIEW) { 6527 Log.v(LOGTAG, "showRectMsg=(top=" + top + ",height=" + 6528 height + ",maxHeight=" + maxHeight + 6529 ",viewHeight=" + viewHeight + ",y=" 6530 + y + ",yPercentInDoc=" + data.mYPercentInDoc + 6531 ",yPercentInView=" + data.mYPercentInView+ ")"); 6532 } 6533 // use the passing content height to cap y as the current 6534 // mContentHeight may not be updated yet 6535 y = Math.max(0, 6536 (Math.min(maxHeight, y + viewHeight) - viewHeight)); 6537 // We need to take into account the visible title height 6538 // when scrolling since y is an absolute view position. 6539 y = Math.max(0, y - getVisibleTitleHeight()); 6540 scrollTo(x, y); 6541 } 6542 break; 6543 6544 case CENTER_FIT_RECT: 6545 Rect r = (Rect)msg.obj; 6546 mZoomManager.mInZoomOverview = false; 6547 centerFitRect(r.left, r.top, r.width(), r.height()); 6548 break; 6549 6550 case SET_SCROLLBAR_MODES: 6551 mHorizontalScrollBarMode = msg.arg1; 6552 mVerticalScrollBarMode = msg.arg2; 6553 break; 6554 6555 case SELECTION_STRING_CHANGED: 6556 if (mAccessibilityInjector != null) { 6557 String selectionString = (String) msg.obj; 6558 mAccessibilityInjector.onSelectionStringChange(selectionString); 6559 } 6560 break; 6561 6562 default: 6563 super.handleMessage(msg); 6564 break; 6565 } 6566 } 6567 } 6568 6569 /** 6570 * Used when receiving messages for REQUEST_KEYBOARD_WITH_SELECTION_MSG_ID 6571 * and UPDATE_TEXT_SELECTION_MSG_ID. Update the selection of WebTextView. 6572 */ 6573 private void updateTextSelectionFromMessage(int nodePointer, 6574 int textGeneration, WebViewCore.TextSelectionData data) { 6575 if (inEditingMode() 6576 && mWebTextView.isSameTextField(nodePointer) 6577 && textGeneration == mTextGeneration) { 6578 mWebTextView.setSelectionFromWebKit(data.mStart, data.mEnd); 6579 } 6580 } 6581 6582 // Class used to use a dropdown for a <select> element 6583 private class InvokeListBox implements Runnable { 6584 // Whether the listbox allows multiple selection. 6585 private boolean mMultiple; 6586 // Passed in to a list with multiple selection to tell 6587 // which items are selected. 6588 private int[] mSelectedArray; 6589 // Passed in to a list with single selection to tell 6590 // where the initial selection is. 6591 private int mSelection; 6592 6593 private Container[] mContainers; 6594 6595 // Need these to provide stable ids to my ArrayAdapter, 6596 // which normally does not have stable ids. (Bug 1250098) 6597 private class Container extends Object { 6598 /** 6599 * Possible values for mEnabled. Keep in sync with OptionStatus in 6600 * WebViewCore.cpp 6601 */ 6602 final static int OPTGROUP = -1; 6603 final static int OPTION_DISABLED = 0; 6604 final static int OPTION_ENABLED = 1; 6605 6606 String mString; 6607 int mEnabled; 6608 int mId; 6609 6610 public String toString() { 6611 return mString; 6612 } 6613 } 6614 6615 /** 6616 * Subclass ArrayAdapter so we can disable OptionGroupLabels, 6617 * and allow filtering. 6618 */ 6619 private class MyArrayListAdapter extends ArrayAdapter<Container> { 6620 public MyArrayListAdapter(Context context, Container[] objects, boolean multiple) { 6621 super(context, 6622 multiple ? com.android.internal.R.layout.select_dialog_multichoice : 6623 com.android.internal.R.layout.select_dialog_singlechoice, 6624 objects); 6625 } 6626 6627 @Override 6628 public View getView(int position, View convertView, 6629 ViewGroup parent) { 6630 // Always pass in null so that we will get a new CheckedTextView 6631 // Otherwise, an item which was previously used as an <optgroup> 6632 // element (i.e. has no check), could get used as an <option> 6633 // element, which needs a checkbox/radio, but it would not have 6634 // one. 6635 convertView = super.getView(position, null, parent); 6636 Container c = item(position); 6637 if (c != null && Container.OPTION_ENABLED != c.mEnabled) { 6638 // ListView does not draw dividers between disabled and 6639 // enabled elements. Use a LinearLayout to provide dividers 6640 LinearLayout layout = new LinearLayout(mContext); 6641 layout.setOrientation(LinearLayout.VERTICAL); 6642 if (position > 0) { 6643 View dividerTop = new View(mContext); 6644 dividerTop.setBackgroundResource( 6645 android.R.drawable.divider_horizontal_bright); 6646 layout.addView(dividerTop); 6647 } 6648 6649 if (Container.OPTGROUP == c.mEnabled) { 6650 // Currently select_dialog_multichoice and 6651 // select_dialog_singlechoice are CheckedTextViews. If 6652 // that changes, the class cast will no longer be valid. 6653 Assert.assertTrue( 6654 convertView instanceof CheckedTextView); 6655 ((CheckedTextView) convertView).setCheckMarkDrawable( 6656 null); 6657 } else { 6658 // c.mEnabled == Container.OPTION_DISABLED 6659 // Draw the disabled element in a disabled state. 6660 convertView.setEnabled(false); 6661 } 6662 6663 layout.addView(convertView); 6664 if (position < getCount() - 1) { 6665 View dividerBottom = new View(mContext); 6666 dividerBottom.setBackgroundResource( 6667 android.R.drawable.divider_horizontal_bright); 6668 layout.addView(dividerBottom); 6669 } 6670 return layout; 6671 } 6672 return convertView; 6673 } 6674 6675 @Override 6676 public boolean hasStableIds() { 6677 // AdapterView's onChanged method uses this to determine whether 6678 // to restore the old state. Return false so that the old (out 6679 // of date) state does not replace the new, valid state. 6680 return false; 6681 } 6682 6683 private Container item(int position) { 6684 if (position < 0 || position >= getCount()) { 6685 return null; 6686 } 6687 return (Container) getItem(position); 6688 } 6689 6690 @Override 6691 public long getItemId(int position) { 6692 Container item = item(position); 6693 if (item == null) { 6694 return -1; 6695 } 6696 return item.mId; 6697 } 6698 6699 @Override 6700 public boolean areAllItemsEnabled() { 6701 return false; 6702 } 6703 6704 @Override 6705 public boolean isEnabled(int position) { 6706 Container item = item(position); 6707 if (item == null) { 6708 return false; 6709 } 6710 return Container.OPTION_ENABLED == item.mEnabled; 6711 } 6712 } 6713 6714 private InvokeListBox(String[] array, int[] enabled, int[] selected) { 6715 mMultiple = true; 6716 mSelectedArray = selected; 6717 6718 int length = array.length; 6719 mContainers = new Container[length]; 6720 for (int i = 0; i < length; i++) { 6721 mContainers[i] = new Container(); 6722 mContainers[i].mString = array[i]; 6723 mContainers[i].mEnabled = enabled[i]; 6724 mContainers[i].mId = i; 6725 } 6726 } 6727 6728 private InvokeListBox(String[] array, int[] enabled, int selection) { 6729 mSelection = selection; 6730 mMultiple = false; 6731 6732 int length = array.length; 6733 mContainers = new Container[length]; 6734 for (int i = 0; i < length; i++) { 6735 mContainers[i] = new Container(); 6736 mContainers[i].mString = array[i]; 6737 mContainers[i].mEnabled = enabled[i]; 6738 mContainers[i].mId = i; 6739 } 6740 } 6741 6742 /* 6743 * Whenever the data set changes due to filtering, this class ensures 6744 * that the checked item remains checked. 6745 */ 6746 private class SingleDataSetObserver extends DataSetObserver { 6747 private long mCheckedId; 6748 private ListView mListView; 6749 private Adapter mAdapter; 6750 6751 /* 6752 * Create a new observer. 6753 * @param id The ID of the item to keep checked. 6754 * @param l ListView for getting and clearing the checked states 6755 * @param a Adapter for getting the IDs 6756 */ 6757 public SingleDataSetObserver(long id, ListView l, Adapter a) { 6758 mCheckedId = id; 6759 mListView = l; 6760 mAdapter = a; 6761 } 6762 6763 public void onChanged() { 6764 // The filter may have changed which item is checked. Find the 6765 // item that the ListView thinks is checked. 6766 int position = mListView.getCheckedItemPosition(); 6767 long id = mAdapter.getItemId(position); 6768 if (mCheckedId != id) { 6769 // Clear the ListView's idea of the checked item, since 6770 // it is incorrect 6771 mListView.clearChoices(); 6772 // Search for mCheckedId. If it is in the filtered list, 6773 // mark it as checked 6774 int count = mAdapter.getCount(); 6775 for (int i = 0; i < count; i++) { 6776 if (mAdapter.getItemId(i) == mCheckedId) { 6777 mListView.setItemChecked(i, true); 6778 break; 6779 } 6780 } 6781 } 6782 } 6783 6784 public void onInvalidate() {} 6785 } 6786 6787 public void run() { 6788 final ListView listView = (ListView) LayoutInflater.from(mContext) 6789 .inflate(com.android.internal.R.layout.select_dialog, null); 6790 final MyArrayListAdapter adapter = new 6791 MyArrayListAdapter(mContext, mContainers, mMultiple); 6792 AlertDialog.Builder b = new AlertDialog.Builder(mContext) 6793 .setView(listView).setCancelable(true) 6794 .setInverseBackgroundForced(true); 6795 6796 if (mMultiple) { 6797 b.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 6798 public void onClick(DialogInterface dialog, int which) { 6799 mWebViewCore.sendMessage( 6800 EventHub.LISTBOX_CHOICES, 6801 adapter.getCount(), 0, 6802 listView.getCheckedItemPositions()); 6803 }}); 6804 b.setNegativeButton(android.R.string.cancel, 6805 new DialogInterface.OnClickListener() { 6806 public void onClick(DialogInterface dialog, int which) { 6807 mWebViewCore.sendMessage( 6808 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 6809 }}); 6810 } 6811 final AlertDialog dialog = b.create(); 6812 listView.setAdapter(adapter); 6813 listView.setFocusableInTouchMode(true); 6814 // There is a bug (1250103) where the checks in a ListView with 6815 // multiple items selected are associated with the positions, not 6816 // the ids, so the items do not properly retain their checks when 6817 // filtered. Do not allow filtering on multiple lists until 6818 // that bug is fixed. 6819 6820 listView.setTextFilterEnabled(!mMultiple); 6821 if (mMultiple) { 6822 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 6823 int length = mSelectedArray.length; 6824 for (int i = 0; i < length; i++) { 6825 listView.setItemChecked(mSelectedArray[i], true); 6826 } 6827 } else { 6828 listView.setOnItemClickListener(new OnItemClickListener() { 6829 public void onItemClick(AdapterView parent, View v, 6830 int position, long id) { 6831 mWebViewCore.sendMessage( 6832 EventHub.SINGLE_LISTBOX_CHOICE, (int)id, 0); 6833 dialog.dismiss(); 6834 } 6835 }); 6836 if (mSelection != -1) { 6837 listView.setSelection(mSelection); 6838 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 6839 listView.setItemChecked(mSelection, true); 6840 DataSetObserver observer = new SingleDataSetObserver( 6841 adapter.getItemId(mSelection), listView, adapter); 6842 adapter.registerDataSetObserver(observer); 6843 } 6844 } 6845 dialog.setOnCancelListener(new DialogInterface.OnCancelListener() { 6846 public void onCancel(DialogInterface dialog) { 6847 mWebViewCore.sendMessage( 6848 EventHub.SINGLE_LISTBOX_CHOICE, -2, 0); 6849 } 6850 }); 6851 dialog.show(); 6852 } 6853 } 6854 6855 /* 6856 * Request a dropdown menu for a listbox with multiple selection. 6857 * 6858 * @param array Labels for the listbox. 6859 * @param enabledArray State for each element in the list. See static 6860 * integers in Container class. 6861 * @param selectedArray Which positions are initally selected. 6862 */ 6863 void requestListBox(String[] array, int[] enabledArray, int[] 6864 selectedArray) { 6865 mPrivateHandler.post( 6866 new InvokeListBox(array, enabledArray, selectedArray)); 6867 } 6868 6869 private void updateZoomRange(WebViewCore.RestoreState restoreState, 6870 int viewWidth, int minPrefWidth, boolean updateZoomOverview) { 6871 if (restoreState.mMinScale == 0) { 6872 if (restoreState.mMobileSite) { 6873 if (minPrefWidth > Math.max(0, viewWidth)) { 6874 mZoomManager.mMinZoomScale = (float) viewWidth / minPrefWidth; 6875 mZoomManager.mMinZoomScaleFixed = false; 6876 if (updateZoomOverview) { 6877 WebSettings settings = getSettings(); 6878 mZoomManager.mInZoomOverview = settings.getUseWideViewPort() && 6879 settings.getLoadWithOverviewMode(); 6880 } 6881 } else { 6882 mZoomManager.mMinZoomScale = restoreState.mDefaultScale; 6883 mZoomManager.mMinZoomScaleFixed = true; 6884 } 6885 } else { 6886 mZoomManager.mMinZoomScale = mZoomManager.DEFAULT_MIN_ZOOM_SCALE; 6887 mZoomManager.mMinZoomScaleFixed = false; 6888 } 6889 } else { 6890 mZoomManager.mMinZoomScale = restoreState.mMinScale; 6891 mZoomManager.mMinZoomScaleFixed = true; 6892 } 6893 if (restoreState.mMaxScale == 0) { 6894 mZoomManager.mMaxZoomScale = mZoomManager.DEFAULT_MAX_ZOOM_SCALE; 6895 } else { 6896 mZoomManager.mMaxZoomScale = restoreState.mMaxScale; 6897 } 6898 } 6899 6900 /* 6901 * Request a dropdown menu for a listbox with single selection or a single 6902 * <select> element. 6903 * 6904 * @param array Labels for the listbox. 6905 * @param enabledArray State for each element in the list. See static 6906 * integers in Container class. 6907 * @param selection Which position is initally selected. 6908 */ 6909 void requestListBox(String[] array, int[] enabledArray, int selection) { 6910 mPrivateHandler.post( 6911 new InvokeListBox(array, enabledArray, selection)); 6912 } 6913 6914 // called by JNI 6915 private void sendMoveFocus(int frame, int node) { 6916 mWebViewCore.sendMessage(EventHub.SET_MOVE_FOCUS, 6917 new WebViewCore.CursorData(frame, node, 0, 0)); 6918 } 6919 6920 // called by JNI 6921 private void sendMoveMouse(int frame, int node, int x, int y) { 6922 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE, 6923 new WebViewCore.CursorData(frame, node, x, y)); 6924 } 6925 6926 /* 6927 * Send a mouse move event to the webcore thread. 6928 * 6929 * @param removeFocus Pass true if the "mouse" cursor is now over a node 6930 * which wants key events, but it is not the focus. This 6931 * will make the visual appear as though nothing is in 6932 * focus. Remove the WebTextView, if present, and stop 6933 * drawing the blinking caret. 6934 * called by JNI 6935 */ 6936 private void sendMoveMouseIfLatest(boolean removeFocus) { 6937 if (removeFocus) { 6938 clearTextEntry(true); 6939 } 6940 mWebViewCore.sendMessage(EventHub.SET_MOVE_MOUSE_IF_LATEST, 6941 cursorData()); 6942 } 6943 6944 // called by JNI 6945 private void sendMotionUp(int touchGeneration, 6946 int frame, int node, int x, int y) { 6947 WebViewCore.TouchUpData touchUpData = new WebViewCore.TouchUpData(); 6948 touchUpData.mMoveGeneration = touchGeneration; 6949 touchUpData.mFrame = frame; 6950 touchUpData.mNode = node; 6951 touchUpData.mX = x; 6952 touchUpData.mY = y; 6953 mWebViewCore.sendMessage(EventHub.TOUCH_UP, touchUpData); 6954 } 6955 6956 6957 private int getScaledMaxXScroll() { 6958 int width; 6959 if (mHeightCanMeasure == false) { 6960 width = getViewWidth() / 4; 6961 } else { 6962 Rect visRect = new Rect(); 6963 calcOurVisibleRect(visRect); 6964 width = visRect.width() / 2; 6965 } 6966 // FIXME the divisor should be retrieved from somewhere 6967 return viewToContentX(width); 6968 } 6969 6970 private int getScaledMaxYScroll() { 6971 int height; 6972 if (mHeightCanMeasure == false) { 6973 height = getViewHeight() / 4; 6974 } else { 6975 Rect visRect = new Rect(); 6976 calcOurVisibleRect(visRect); 6977 height = visRect.height() / 2; 6978 } 6979 // FIXME the divisor should be retrieved from somewhere 6980 // the closest thing today is hard-coded into ScrollView.java 6981 // (from ScrollView.java, line 363) int maxJump = height/2; 6982 return Math.round(height * mInvActualScale); 6983 } 6984 6985 /** 6986 * Called by JNI to invalidate view 6987 */ 6988 private void viewInvalidate() { 6989 invalidate(); 6990 } 6991 6992 /** 6993 * Pass the key directly to the page. This assumes that 6994 * nativePageShouldHandleShiftAndArrows() returned true. 6995 */ 6996 private void letPageHandleNavKey(int keyCode, long time, boolean down) { 6997 int keyEventAction; 6998 int eventHubAction; 6999 if (down) { 7000 keyEventAction = KeyEvent.ACTION_DOWN; 7001 eventHubAction = EventHub.KEY_DOWN; 7002 playSoundEffect(keyCodeToSoundsEffect(keyCode)); 7003 } else { 7004 keyEventAction = KeyEvent.ACTION_UP; 7005 eventHubAction = EventHub.KEY_UP; 7006 } 7007 KeyEvent event = new KeyEvent(time, time, keyEventAction, keyCode, 7008 1, (mShiftIsPressed ? KeyEvent.META_SHIFT_ON : 0) 7009 | (false ? KeyEvent.META_ALT_ON : 0) // FIXME 7010 | (false ? KeyEvent.META_SYM_ON : 0) // FIXME 7011 , 0, 0, 0); 7012 mWebViewCore.sendMessage(eventHubAction, event); 7013 } 7014 7015 // return true if the key was handled 7016 private boolean navHandledKey(int keyCode, int count, boolean noScroll, 7017 long time) { 7018 if (mNativeClass == 0) { 7019 return false; 7020 } 7021 mLastCursorTime = time; 7022 mLastCursorBounds = nativeGetCursorRingBounds(); 7023 boolean keyHandled 7024 = nativeMoveCursor(keyCode, count, noScroll) == false; 7025 if (DebugFlags.WEB_VIEW) { 7026 Log.v(LOGTAG, "navHandledKey mLastCursorBounds=" + mLastCursorBounds 7027 + " mLastCursorTime=" + mLastCursorTime 7028 + " handled=" + keyHandled); 7029 } 7030 if (keyHandled == false || mHeightCanMeasure == false) { 7031 return keyHandled; 7032 } 7033 Rect contentCursorRingBounds = nativeGetCursorRingBounds(); 7034 if (contentCursorRingBounds.isEmpty()) return keyHandled; 7035 Rect viewCursorRingBounds = contentToViewRect(contentCursorRingBounds); 7036 Rect visRect = new Rect(); 7037 calcOurVisibleRect(visRect); 7038 Rect outset = new Rect(visRect); 7039 int maxXScroll = visRect.width() / 2; 7040 int maxYScroll = visRect.height() / 2; 7041 outset.inset(-maxXScroll, -maxYScroll); 7042 if (Rect.intersects(outset, viewCursorRingBounds) == false) { 7043 return keyHandled; 7044 } 7045 // FIXME: Necessary because ScrollView/ListView do not scroll left/right 7046 int maxH = Math.min(viewCursorRingBounds.right - visRect.right, 7047 maxXScroll); 7048 if (maxH > 0) { 7049 pinScrollBy(maxH, 0, true, 0); 7050 } else { 7051 maxH = Math.max(viewCursorRingBounds.left - visRect.left, 7052 -maxXScroll); 7053 if (maxH < 0) { 7054 pinScrollBy(maxH, 0, true, 0); 7055 } 7056 } 7057 if (mLastCursorBounds.isEmpty()) return keyHandled; 7058 if (mLastCursorBounds.equals(contentCursorRingBounds)) { 7059 return keyHandled; 7060 } 7061 if (DebugFlags.WEB_VIEW) { 7062 Log.v(LOGTAG, "navHandledKey contentCursorRingBounds=" 7063 + contentCursorRingBounds); 7064 } 7065 requestRectangleOnScreen(viewCursorRingBounds); 7066 mUserScroll = true; 7067 return keyHandled; 7068 } 7069 7070 /** 7071 * Set the background color. It's white by default. Pass 7072 * zero to make the view transparent. 7073 * @param color the ARGB color described by Color.java 7074 */ 7075 public void setBackgroundColor(int color) { 7076 mBackgroundColor = color; 7077 mWebViewCore.sendMessage(EventHub.SET_BACKGROUND_COLOR, color); 7078 } 7079 7080 public void debugDump() { 7081 nativeDebugDump(); 7082 mWebViewCore.sendMessage(EventHub.DUMP_NAVTREE); 7083 } 7084 7085 /** 7086 * Draw the HTML page into the specified canvas. This call ignores any 7087 * view-specific zoom, scroll offset, or other changes. It does not draw 7088 * any view-specific chrome, such as progress or URL bars. 7089 * 7090 * @hide only needs to be accessible to Browser and testing 7091 */ 7092 public void drawPage(Canvas canvas) { 7093 mWebViewCore.drawContentPicture(canvas, 0, false, false); 7094 } 7095 7096 /** 7097 * Set the time to wait between passing touches to WebCore. See also the 7098 * TOUCH_SENT_INTERVAL member for further discussion. 7099 * 7100 * @hide This is only used by the DRT test application. 7101 */ 7102 public void setTouchInterval(int interval) { 7103 mCurrentTouchInterval = interval; 7104 } 7105 7106 /** 7107 * Update our cache with updatedText. 7108 * @param updatedText The new text to put in our cache. 7109 */ 7110 /* package */ void updateCachedTextfield(String updatedText) { 7111 // Also place our generation number so that when we look at the cache 7112 // we recognize that it is up to date. 7113 nativeUpdateCachedTextfield(updatedText, mTextGeneration); 7114 } 7115 7116 private native int nativeCacheHitFramePointer(); 7117 private native Rect nativeCacheHitNodeBounds(); 7118 private native int nativeCacheHitNodePointer(); 7119 /* package */ native void nativeClearCursor(); 7120 private native void nativeCreate(int ptr); 7121 private native int nativeCursorFramePointer(); 7122 private native Rect nativeCursorNodeBounds(); 7123 private native int nativeCursorNodePointer(); 7124 /* package */ native boolean nativeCursorMatchesFocus(); 7125 private native boolean nativeCursorIntersects(Rect visibleRect); 7126 private native boolean nativeCursorIsAnchor(); 7127 private native boolean nativeCursorIsTextInput(); 7128 private native Point nativeCursorPosition(); 7129 private native String nativeCursorText(); 7130 /** 7131 * Returns true if the native cursor node says it wants to handle key events 7132 * (ala plugins). This can only be called if mNativeClass is non-zero! 7133 */ 7134 private native boolean nativeCursorWantsKeyEvents(); 7135 private native void nativeDebugDump(); 7136 private native void nativeDestroy(); 7137 private native boolean nativeEvaluateLayersAnimations(); 7138 private native void nativeDrawExtras(Canvas canvas, int extra); 7139 private native void nativeDumpDisplayTree(String urlOrNull); 7140 private native int nativeFindAll(String findLower, String findUpper); 7141 private native void nativeFindNext(boolean forward); 7142 /* package */ native int nativeFocusCandidateFramePointer(); 7143 /* package */ native boolean nativeFocusCandidateHasNextTextfield(); 7144 /* package */ native boolean nativeFocusCandidateIsPassword(); 7145 private native boolean nativeFocusCandidateIsRtlText(); 7146 private native boolean nativeFocusCandidateIsTextInput(); 7147 /* package */ native int nativeFocusCandidateMaxLength(); 7148 /* package */ native String nativeFocusCandidateName(); 7149 private native Rect nativeFocusCandidateNodeBounds(); 7150 /* package */ native int nativeFocusCandidatePointer(); 7151 private native String nativeFocusCandidateText(); 7152 private native int nativeFocusCandidateTextSize(); 7153 /** 7154 * Returns an integer corresponding to WebView.cpp::type. 7155 * See WebTextView.setType() 7156 */ 7157 private native int nativeFocusCandidateType(); 7158 private native boolean nativeFocusIsPlugin(); 7159 private native Rect nativeFocusNodeBounds(); 7160 /* package */ native int nativeFocusNodePointer(); 7161 private native Rect nativeGetCursorRingBounds(); 7162 private native String nativeGetSelection(); 7163 private native boolean nativeHasCursorNode(); 7164 private native boolean nativeHasFocusNode(); 7165 private native void nativeHideCursor(); 7166 private native String nativeImageURI(int x, int y); 7167 private native void nativeInstrumentReport(); 7168 /* package */ native boolean nativeMoveCursorToNextTextInput(); 7169 // return true if the page has been scrolled 7170 private native boolean nativeMotionUp(int x, int y, int slop); 7171 // returns false if it handled the key 7172 private native boolean nativeMoveCursor(int keyCode, int count, 7173 boolean noScroll); 7174 private native int nativeMoveGeneration(); 7175 private native void nativeMoveSelection(int x, int y, 7176 boolean extendSelection); 7177 /** 7178 * @return true if the page should get the shift and arrow keys, rather 7179 * than select text/navigation. 7180 * 7181 * If the focus is a plugin, or if the focus and cursor match and are 7182 * a contentEditable element, then the page should handle these keys. 7183 */ 7184 private native boolean nativePageShouldHandleShiftAndArrows(); 7185 private native boolean nativePointInNavCache(int x, int y, int slop); 7186 // Like many other of our native methods, you must make sure that 7187 // mNativeClass is not null before calling this method. 7188 private native void nativeRecordButtons(boolean focused, 7189 boolean pressed, boolean invalidate); 7190 private native void nativeSelectBestAt(Rect rect); 7191 private native int nativeFindIndex(); 7192 private native void nativeSetFindIsEmpty(); 7193 private native void nativeSetFindIsUp(boolean isUp); 7194 private native void nativeSetFollowedLink(boolean followed); 7195 private native void nativeSetHeightCanMeasure(boolean measure); 7196 private native void nativeSetRootLayer(int layer); 7197 private native void nativeSetSelectionPointer(boolean set, 7198 float scale, int x, int y, boolean extendSelection); 7199 private native void nativeSetSelectionRegion(boolean set); 7200 private native Rect nativeSubtractLayers(Rect content); 7201 private native int nativeTextGeneration(); 7202 // Never call this version except by updateCachedTextfield(String) - 7203 // we always want to pass in our generation number. 7204 private native void nativeUpdateCachedTextfield(String updatedText, 7205 int generation); 7206 // return NO_LEFTEDGE means failure. 7207 private static final int NO_LEFTEDGE = -1; 7208 private native int nativeGetBlockLeftEdge(int x, int y, float scale); 7209} 7210