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